Add V2 implementation of payments:

Add a new algorithm for finding the liquidity in a payment path. There
is still a reverse and forward pass, but the forward pass starts at the
limiting step rather than the payment source. This insures the limiting
step is completely consumed rather than potentially leaving a 'dust'
amount in the forward pass.

Each step in a payment is either a book step, a direct step (account to
account step), or an xrp endpoint. Each step in the existing
implementation is a triple, where each element in the triple is either
an account of a book, for a total of eight step types.

Since accounts are considered in pairs, rather than triples, transfer
fees are handled differently. In V1 of payments, in the payment path
A -> gw ->B, if A redeems to gw, and gw issues to B, a transfer fee is
changed. In the new code, a transfer fee is changed even if A issues to
gw.
This commit is contained in:
seelabs
2015-11-15 13:20:26 -05:00
parent f3e93bbbeb
commit 122a5cdf89
44 changed files with 4654 additions and 223 deletions

View File

@@ -1424,6 +1424,36 @@
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ripple\app\paths\cursor\RippleLiquidity.h"> <ClInclude Include="..\..\src\ripple\app\paths\cursor\RippleLiquidity.h">
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ripple\app\paths\Flow.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\paths\Flow.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\paths\impl\AmountSpec.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\paths\impl\BookStep.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\paths\impl\DirectStep.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\paths\impl\PaySteps.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\paths\impl\StepChecks.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\paths\impl\Steps.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\paths\impl\StrandFlow.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\paths\impl\XRPEndpointStep.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\paths\Node.cpp"> <ClCompile Include="..\..\src\ripple\app\paths\Node.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -1494,6 +1524,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\ripple\app\tests\Flow_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\tests\HashRouter_test.cpp"> <ClCompile Include="..\..\src\ripple\app\tests\HashRouter_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -2552,7 +2586,7 @@
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ripple\protocol\AccountID.h"> <ClInclude Include="..\..\src\ripple\protocol\AccountID.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\protocol\AmountSpec.h"> <ClInclude Include="..\..\src\ripple\protocol\AmountConversions.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\protocol\Book.h"> <ClInclude Include="..\..\src\ripple\protocol\Book.h">
</ClInclude> </ClInclude>

View File

@@ -235,6 +235,9 @@
<Filter Include="ripple\app\paths\cursor"> <Filter Include="ripple\app\paths\cursor">
<UniqueIdentifier>{9AD8D049-10A8-704C-D51A-FAD55B1F235F}</UniqueIdentifier> <UniqueIdentifier>{9AD8D049-10A8-704C-D51A-FAD55B1F235F}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="ripple\app\paths\impl">
<UniqueIdentifier>{38932157-7DA1-A9CC-CABC-2A3D9CACF188}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\app\tests"> <Filter Include="ripple\app\tests">
<UniqueIdentifier>{2E791662-6ED0-D1E1-03A4-0CB35473EC56}</UniqueIdentifier> <UniqueIdentifier>{2E791662-6ED0-D1E1-03A4-0CB35473EC56}</UniqueIdentifier>
</Filter> </Filter>
@@ -2016,6 +2019,36 @@
<ClInclude Include="..\..\src\ripple\app\paths\cursor\RippleLiquidity.h"> <ClInclude Include="..\..\src\ripple\app\paths\cursor\RippleLiquidity.h">
<Filter>ripple\app\paths\cursor</Filter> <Filter>ripple\app\paths\cursor</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ripple\app\paths\Flow.cpp">
<Filter>ripple\app\paths</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\paths\Flow.h">
<Filter>ripple\app\paths</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\paths\impl\AmountSpec.h">
<Filter>ripple\app\paths\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\paths\impl\BookStep.cpp">
<Filter>ripple\app\paths\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\paths\impl\DirectStep.cpp">
<Filter>ripple\app\paths\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\paths\impl\PaySteps.cpp">
<Filter>ripple\app\paths\impl</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\paths\impl\StepChecks.h">
<Filter>ripple\app\paths\impl</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\paths\impl\Steps.h">
<Filter>ripple\app\paths\impl</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\paths\impl\StrandFlow.h">
<Filter>ripple\app\paths\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\paths\impl\XRPEndpointStep.cpp">
<Filter>ripple\app\paths\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\paths\Node.cpp"> <ClCompile Include="..\..\src\ripple\app\paths\Node.cpp">
<Filter>ripple\app\paths</Filter> <Filter>ripple\app\paths</Filter>
</ClCompile> </ClCompile>
@@ -2085,6 +2118,9 @@
<ClCompile Include="..\..\src\ripple\app\tests\DeliverMin.test.cpp"> <ClCompile Include="..\..\src\ripple\app\tests\DeliverMin.test.cpp">
<Filter>ripple\app\tests</Filter> <Filter>ripple\app\tests</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\ripple\app\tests\Flow_test.cpp">
<Filter>ripple\app\tests</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\tests\HashRouter_test.cpp"> <ClCompile Include="..\..\src\ripple\app\tests\HashRouter_test.cpp">
<Filter>ripple\app\tests</Filter> <Filter>ripple\app\tests</Filter>
</ClCompile> </ClCompile>
@@ -3099,7 +3135,7 @@
<ClInclude Include="..\..\src\ripple\protocol\AccountID.h"> <ClInclude Include="..\..\src\ripple\protocol\AccountID.h">
<Filter>ripple\protocol</Filter> <Filter>ripple\protocol</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\protocol\AmountSpec.h"> <ClInclude Include="..\..\src\ripple\protocol\AmountConversions.h">
<Filter>ripple\protocol</Filter> <Filter>ripple\protocol</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\protocol\Book.h"> <ClInclude Include="..\..\src\ripple\protocol\Book.h">

View File

@@ -19,7 +19,7 @@
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/ledger/ReadView.h> #include <ripple/ledger/ReadView.h>
#include <ripple/protocol/AmountSpec.h> #include <ripple/protocol/AmountConversions.h>
#include <ripple/protocol/STAmount.h> #include <ripple/protocol/STAmount.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>

View File

@@ -33,6 +33,7 @@ namespace ripple {
@param currency the IOU to check. @param currency the IOU to check.
@return The maximum amount that can be held. @return The maximum amount that can be held.
*/ */
/** @{ */
STAmount creditLimit ( STAmount creditLimit (
ReadView const& view, ReadView const& view,
AccountID const& account, AccountID const& account,
@@ -45,6 +46,7 @@ creditLimit2 (
AccountID const& acc, AccountID const& acc,
AccountID const& iss, AccountID const& iss,
Currency const& cur); Currency const& cur);
/** @} */
/** Returns the amount of IOUs issued by issuer that are held by an account /** Returns the amount of IOUs issued by issuer that are held by an account
@param ledger the ledger to check against. @param ledger the ledger to check against.
@@ -52,6 +54,7 @@ creditLimit2 (
@param issuer the issuer of the IOU. @param issuer the issuer of the IOU.
@param currency the IOU to check. @param currency the IOU to check.
*/ */
/** @{ */
STAmount creditBalance ( STAmount creditBalance (
ReadView const& view, ReadView const& view,
AccountID const& account, AccountID const& account,
@@ -64,6 +67,7 @@ creditBalance2 (
AccountID const& acc, AccountID const& acc,
AccountID const& iss, AccountID const& iss,
Currency const& cur); Currency const& cur);
/** @} */
} // ripple } // ripple

View File

@@ -0,0 +1,154 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/paths/Credit.h>
#include <ripple/app/paths/Flow.h>
#include <ripple/app/paths/impl/AmountSpec.h>
#include <ripple/app/paths/impl/StrandFlow.h>
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/XRPAmount.h>
#include <numeric>
#include <sstream>
namespace ripple {
path::RippleCalc::Output
flow (
PaymentSandbox& sb,
STAmount const& deliver,
AccountID const& src,
AccountID const& dst,
STPathSet const& paths,
bool defaultPaths,
bool partialPayment,
boost::optional<Quality> const& limitQuality,
boost::optional<STAmount> const& sendMax,
beast::Journal j)
{
path::RippleCalc::Output result;
Issue const srcIssue =
sendMax ? sendMax->issue () : Issue (deliver.issue ().currency, src);
Issue const dstIssue = deliver.issue ();
boost::optional<Issue> sendMaxIssue;
if (sendMax)
sendMaxIssue = sendMax->issue ();
// convert the paths to a collection of strands. Each strand is the collection
// of account->account steps and book steps that may be used in this payment.
auto sr = toStrands (sb, src, dst, dstIssue, sendMaxIssue, paths,
defaultPaths, j);
if (sr.first != tesSUCCESS)
{
result.setResult (sr.first);
return result;
}
auto& strands = sr.second;
if (j.trace)
{
j.trace << "\nsrc: " << src << "\ndst: " << dst
<< "\nsrcIssue: " << srcIssue << "\ndstIssue: " << dstIssue;
j.trace << "\nNumStrands: " << strands.size ();
for (auto const& curStrand : strands)
{
j.trace << "NumSteps: " << curStrand.size ();
for (auto const& step : curStrand)
{
j.trace << '\n' << *step << '\n';
}
}
}
const bool srcIsXRP = isXRP (srcIssue.currency);
const bool dstIsXRP = isXRP (dstIssue.currency);
auto const asDeliver = toAmountSpec (deliver);
boost::optional<PaymentSandbox> strandSB;
// The src account may send either xrp or iou. The dst account may receive
// either xrp or iou. Since XRP and IOU amounts are represented by different
// types, use templates to tell `flow` about the amount types.
if (srcIsXRP && dstIsXRP)
{
auto f = flow<XRPAmount, XRPAmount> (
sb, strands, asDeliver.xrp, defaultPaths, partialPayment, limitQuality, sendMax, j);
if (f.ter == tesSUCCESS)
strandSB.emplace (std::move (*f.sandbox));
result.setResult (f.ter);
result.actualAmountIn = toSTAmount (f.in);
result.actualAmountOut = toSTAmount (f.out);
}
else if (srcIsXRP && !dstIsXRP)
{
auto f = flow<XRPAmount, IOUAmount> (
sb, strands, asDeliver.iou, defaultPaths, partialPayment,
limitQuality, sendMax, j);
if (f.ter == tesSUCCESS)
strandSB.emplace (std::move (*f.sandbox));
result.setResult (f.ter);
result.actualAmountIn = toSTAmount (f.in);
result.actualAmountOut = toSTAmount (f.out, dstIssue);
}
else if (!srcIsXRP && dstIsXRP)
{
auto f = flow<IOUAmount, XRPAmount> (
sb, strands, asDeliver.xrp, defaultPaths, partialPayment,
limitQuality, sendMax, j);
if (f.ter == tesSUCCESS)
strandSB.emplace (std::move (*f.sandbox));
result.setResult (f.ter);
result.actualAmountIn = toSTAmount (f.in, srcIssue);
result.actualAmountOut = toSTAmount (f.out);
}
else if (!srcIsXRP && !dstIsXRP)
{
auto f = flow<IOUAmount, IOUAmount> (
sb, strands, asDeliver.iou, defaultPaths, partialPayment,
limitQuality, sendMax, j);
if (f.ter == tesSUCCESS)
strandSB.emplace (std::move (*f.sandbox));
result.setResult (f.ter);
result.actualAmountIn = toSTAmount (f.in, srcIssue);
result.actualAmountOut = toSTAmount (f.out, dstIssue);
}
// strandSB is only valid when flow was successful
if (strandSB)
strandSB->apply (sb);
return result;
}
} // ripple

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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_APP_PATHS_FLOW_H_INCLUDED
#define RIPPLE_APP_PATHS_FLOW_H_INCLUDED
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/app/paths/RippleCalc.h>
#include <ripple/protocol/Quality.h>
namespace ripple
{
/**
Make a payment from the src account to the dst account
@param view Trust lines and balances
@param deliver Amount to deliver to the dst account
@param src Account providing input funds for the payment
@param dst Account receiving the payment
@param paths Set of paths to explore for liquidity
@param defaultPaths Include defaultPaths in the path set
@param partialPayment If the payment cannot deliver the entire
requested amount, deliver as much as possible, given the constraints
@param limitQuality Do not use liquidity below this quality threshold
@param sendMax Do not spend more than this amount
@param logs Logs to write journal messages to
@return Actual amount in and out, and the result code
*/
path::RippleCalc::Output
flow (PaymentSandbox& view,
STAmount const& deliver,
AccountID const& src,
AccountID const& dst,
STPathSet const& paths,
bool defaultPaths,
bool partialPayment,
boost::optional<Quality> const& limitQuality,
boost::optional<STAmount> const& sendMax,
beast::Journal j);
} // ripple
#endif

View File

@@ -524,6 +524,7 @@ PathRequest::findPaths (std::shared_ptr<RippleLineCache> const& cache, int const
*raSrcAccount, // --> Account sending from. *raSrcAccount, // --> Account sending from.
ps, // --> Path set. ps, // --> Path set.
app_.logs(), app_.logs(),
app_.config(),
&rcInput); &rcInput);
if (! convert_all_ && if (! convert_all_ &&
@@ -544,7 +545,8 @@ PathRequest::findPaths (std::shared_ptr<RippleLineCache> const& cache, int const
*raDstAccount, // --> Account to deliver to. *raDstAccount, // --> Account to deliver to.
*raSrcAccount, // --> Account sending from. *raSrcAccount, // --> Account sending from.
ps, // --> Path set. ps, // --> Path set.
app_.logs()); app_.logs(),
app_.config());
if (rc.result() != tesSUCCESS) if (rc.result() != tesSUCCESS)
{ {

View File

@@ -28,6 +28,7 @@
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/json/to_string.h> #include <ripple/json/to_string.h>
#include <ripple/core/JobQueue.h> #include <ripple/core/JobQueue.h>
#include <ripple/core/Config.h>
#include <tuple> #include <tuple>
/* /*
@@ -354,6 +355,7 @@ TER Pathfinder::getPathLiquidity (
mSrcAccount, mSrcAccount,
pathSet, pathSet,
app_.logs(), app_.logs(),
app_.config(),
&rcInput); &rcInput);
// If we can't get even the minimum liquidity requested, we're done. // If we can't get even the minimum liquidity requested, we're done.
if (rc.result () != tesSUCCESS) if (rc.result () != tesSUCCESS)
@@ -374,6 +376,7 @@ TER Pathfinder::getPathLiquidity (
mSrcAccount, mSrcAccount,
pathSet, pathSet,
app_.logs (), app_.logs (),
app_.config (),
&rcInput); &rcInput);
// If we found further liquidity, add it into the result. // If we found further liquidity, add it into the result.
@@ -425,6 +428,7 @@ void Pathfinder::computePathRanks (int maxPaths)
mSrcAccount, mSrcAccount,
STPathSet(), STPathSet(),
app_.logs (), app_.logs (),
app_.config (),
&rcInput); &rcInput);
if (rc.result () == tesSUCCESS) if (rc.result () == tesSUCCESS)

View File

@@ -18,17 +18,18 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/paths/Tuning.h> #include <ripple/app/paths/Flow.h>
#include <ripple/app/paths/RippleCalc.h> #include <ripple/app/paths/RippleCalc.h>
#include <ripple/app/paths/Tuning.h>
#include <ripple/app/paths/cursor/PathCursor.h> #include <ripple/app/paths/cursor/PathCursor.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/ledger/View.h> #include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
namespace ripple { namespace ripple {
namespace path { namespace path {
namespace {
static static
TER TER
deleteOffers (ApplyView& view, deleteOffers (ApplyView& view,
@@ -41,8 +42,6 @@ deleteOffers (ApplyView& view,
return tesSUCCESS; return tesSUCCESS;
} }
} // namespace
RippleCalc::Output RippleCalc::rippleCalculate ( RippleCalc::Output RippleCalc::rippleCalculate (
PaymentSandbox& view, PaymentSandbox& view,
@@ -68,29 +67,110 @@ RippleCalc::Output RippleCalc::rippleCalculate (
// explore for liquidity. // explore for liquidity.
STPathSet const& spsPaths, STPathSet const& spsPaths,
Logs& l, Logs& l,
Config const& config,
Input const* const pInputs) Input const* const pInputs)
{ {
RippleCalc rc ( bool const useFlowV1Output =
view, !flowV2Switchover (view.info ().parentCloseTime) &&
saMaxAmountReq, !view.rules ().enabled (featureFlowV2, config.features);
saDstAmountReq, // When flowV2 is enabled via rules, call old flow so results may be
uDstAccountID, // compared
uSrcAccountID, bool const callFlowV1 = useFlowV1Output ||
spsPaths, view.rules ().enabled (featureFlowV2, config.features);
l); bool const callFlowV2 = !useFlowV1Output;
if (pInputs != nullptr)
Output flowV1Out;
PaymentSandbox flowV1SB (&view);
if (callFlowV1)
{ {
rc.inputFlags = *pInputs; RippleCalc rc (
flowV1SB,
saMaxAmountReq,
saDstAmountReq,
uDstAccountID,
uSrcAccountID,
spsPaths,
l);
if (pInputs != nullptr)
{
rc.inputFlags = *pInputs;
}
auto result = rc.rippleCalculate ();
flowV1Out.setResult (result);
flowV1Out.actualAmountIn = rc.actualAmountIn_;
flowV1Out.actualAmountOut = rc.actualAmountOut_;
} }
auto result = rc.rippleCalculate (); Output flowV2Out;
Output output; PaymentSandbox flowV2SB (&view);
output.setResult (result); if (callFlowV2)
output.actualAmountIn = rc.actualAmountIn_; {
output.actualAmountOut = rc.actualAmountOut_; bool defaultPaths = true;
output.pathStateList = rc.pathStateList_; bool partialPayment = false;
boost::optional<Quality> limitQuality;
boost::optional<STAmount> sendMax;
return output; if (pInputs)
{
defaultPaths = pInputs->defaultPathsAllowed;
partialPayment = pInputs->partialPaymentAllowed;
if (pInputs->limitQuality && saMaxAmountReq > beast::zero)
limitQuality.emplace (
Amounts (saMaxAmountReq, saDstAmountReq));
}
if (saMaxAmountReq >= beast::zero ||
saMaxAmountReq.getCurrency () != saDstAmountReq.getCurrency () ||
saMaxAmountReq.getIssuer () != uSrcAccountID)
{
sendMax.emplace (saMaxAmountReq);
}
auto j = l.journal ("Flow");
try
{
flowV2Out = flow (flowV2SB, saDstAmountReq, uSrcAccountID,
uDstAccountID, spsPaths, defaultPaths, partialPayment,
limitQuality, sendMax, j);
}
catch (std::exception& e)
{
JLOG (j.trace) << "Exception from flow" << e.what ();
if (!useFlowV1Output)
throw;
}
if (callFlowV2 && callFlowV1 &&
(flowV2Out.result () != flowV1Out.result () ||
(flowV2Out.result () == tesSUCCESS &&
(flowV2Out.actualAmountIn != flowV1Out.actualAmountIn ||
flowV2Out.actualAmountOut != flowV1Out.actualAmountOut))))
{
JLOG (j.trace) <<
"Mismatch: New Flow and RippleCalc" <<
" Old actualIn: " << flowV1Out.actualAmountIn <<
" New actualIn: " << flowV2Out.actualAmountIn <<
" Old actualOut: " << flowV1Out.actualAmountOut <<
" New actualOut: " << flowV2Out.actualAmountOut <<
" Old result: " << flowV1Out.result () <<
" New result: " << flowV2Out.result();
}
else
{
JLOG (j.trace) << "Match: New Flow and RippleCalc";
}
JLOG (j.trace) << "Using old flow: " << useFlowV1Output;
}
if (!useFlowV1Output)
{
flowV2SB.apply (view);
return flowV2Out;
}
flowV1SB.apply (view);
return flowV1Out;
} }
bool RippleCalc::addPathState(STPath const& path, TER& resultCode) bool RippleCalc::addPathState(STPath const& path, TER& resultCode)
@@ -269,6 +349,12 @@ TER RippleCalc::rippleCalculate ()
assert (pathState->inPass() && pathState->outPass()); assert (pathState->inPass() && pathState->outPass());
JLOG (j_.debug)
<< "Old flow iter (iter, in, out): "
<< iPass << " "
<< pathState->inPass() << " "
<< pathState->outPass();
if ((!inputFlags.limitQuality || if ((!inputFlags.limitQuality ||
pathState->quality() <= uQualityLimit) pathState->quality() <= uQualityLimit)
// Quality is not limited or increment has allowed // Quality is not limited or increment has allowed
@@ -325,7 +411,8 @@ TER RippleCalc::rippleCalculate ()
<< " uQuality=" << " uQuality="
<< amountFromRate (pathState->quality()) << amountFromRate (pathState->quality())
<< " inPass()=" << pathState->inPass() << " inPass()=" << pathState->inPass()
<< " saOutPass=" << pathState->outPass(); << " saOutPass=" << pathState->outPass()
<< " iBest=" << iBest;
// Record best pass' offers that became unfunded for deletion on // Record best pass' offers that became unfunded for deletion on
// success. // success.
@@ -340,6 +427,16 @@ TER RippleCalc::rippleCalculate ()
actualAmountIn_ += pathState->inPass(); actualAmountIn_ += pathState->inPass();
actualAmountOut_ += pathState->outPass(); actualAmountOut_ += pathState->outPass();
JLOG (j_.trace)
<< "rippleCalc: best:"
<< " uQuality="
<< amountFromRate (pathState->quality())
<< " inPass()=" << pathState->inPass()
<< " saOutPass=" << pathState->outPass()
<< " actualIn=" << actualAmountIn_
<< " actualOut=" << actualAmountOut_
<< " iBest=" << iBest;
if (multiQuality) if (multiQuality)
{ {
++iDry; ++iDry;

View File

@@ -27,6 +27,7 @@
#include <ripple/protocol/TER.h> #include <ripple/protocol/TER.h>
namespace ripple { namespace ripple {
class Config;
namespace path { namespace path {
/** RippleCalc calculates the quality of a payment path. /** RippleCalc calculates the quality of a payment path.
@@ -42,7 +43,6 @@ public:
bool partialPaymentAllowed = false; bool partialPaymentAllowed = false;
bool defaultPathsAllowed = true; bool defaultPathsAllowed = true;
bool limitQuality = false; bool limitQuality = false;
bool deleteUnfundedOffers = false;
bool isLedgerOpen = true; bool isLedgerOpen = true;
}; };
struct Output struct Output
@@ -53,13 +53,8 @@ public:
// The computed output amount. // The computed output amount.
STAmount actualAmountOut; STAmount actualAmountOut;
// Expanded path with all the actual nodes in it.
// A path starts with the source account, ends with the destination account
// and goes through other acounts or order books.
PathState::List pathStateList;
private: private:
TER calculationResult_; TER calculationResult_ = temUNKNOWN;
public: public:
TER result () const TER result () const
@@ -100,6 +95,7 @@ public:
// explore for liquidity. // explore for liquidity.
STPathSet const& spsPaths, STPathSet const& spsPaths,
Logs& l, Logs& l,
Config const& config,
Input const* const pInputs = nullptr); Input const* const pInputs = nullptr);
// The view we are currently working on // The view we are currently working on

View File

@@ -17,8 +17,8 @@
*/ */
//============================================================================== //==============================================================================
#ifndef RIPPLE_PROTOCOL_AMOUNTSPEC_H_INCLUDED #ifndef RIPPLE_PATH_IMPL_AMOUNTSPEC_H_INCLUDED
#define RIPPLE_PROTOCOL_AMOUNTSPEC_H_INCLUDED #define RIPPLE_PATH_IMPL_AMOUNTSPEC_H_INCLUDED
#include <ripple/protocol/IOUAmount.h> #include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/XRPAmount.h> #include <ripple/protocol/XRPAmount.h>
@@ -37,7 +37,6 @@ struct AmountSpec
boost::optional<AccountID> issuer; boost::optional<AccountID> issuer;
boost::optional<Currency> currency; boost::optional<Currency> currency;
private:
friend friend
std::ostream& std::ostream&
operator << ( operator << (
@@ -204,86 +203,6 @@ toAmountSpec (
return r; return r;
} }
inline
STAmount
toSTAmount (IOUAmount const& iou, Issue const& iss)
{
bool const isNeg = iou.signum() < 0;
std::uint64_t const umant = isNeg ? - iou.mantissa () : iou.mantissa ();
return STAmount (iss, umant, iou.exponent (), /*native*/ false, isNeg,
STAmount::unchecked ());
}
inline
STAmount
toSTAmount (IOUAmount const& iou)
{
return toSTAmount (iou, noIssue ());
}
inline
STAmount
toSTAmount (XRPAmount const& xrp)
{
bool const isNeg = xrp.signum() < 0;
std::uint64_t const umant = isNeg ? - xrp.drops () : xrp.drops ();
return STAmount (umant, isNeg);
}
inline
STAmount
toSTAmount (XRPAmount const& xrp, Issue const& iss)
{
assert (isXRP(iss.account) && isXRP(iss.currency));
return toSTAmount (xrp);
}
template <class T>
T
toAmount (STAmount const& amt)
{
static_assert(sizeof(T) == -1, "Must used specialized function");
return T(0);
}
template <>
inline
STAmount
toAmount<STAmount> (STAmount const& amt)
{
return amt;
}
template <>
inline
IOUAmount
toAmount<IOUAmount> (STAmount const& amt)
{
assert (amt.mantissa () < std::numeric_limits<std::int64_t>::max ());
bool const isNeg = amt.negative ();
std::int64_t const sMant =
isNeg ? - std::int64_t (amt.mantissa ()) : amt.mantissa ();
assert (! isXRP (amt));
return IOUAmount (sMant, amt.exponent ());
}
template <>
inline
XRPAmount
toAmount<XRPAmount> (STAmount const& amt)
{
assert (amt.mantissa () < std::numeric_limits<std::int64_t>::max ());
bool const isNeg = amt.negative ();
std::int64_t const sMant =
isNeg ? - std::int64_t (amt.mantissa ()) : amt.mantissa ();
AmountSpec result;
assert (isXRP (amt));
return XRPAmount (sMant);
}
} }
#endif #endif

View File

@@ -0,0 +1,613 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/app/paths/Credit.h>
#include <ripple/app/paths/NodeDirectory.h>
#include <ripple/app/tx/impl/OfferStream.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/Directory.h>
#include <ripple/ledger/PaymentSandbox.h>
#include <ripple/protocol/Book.h>
#include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/Quality.h>
#include <ripple/protocol/XRPAmount.h>
#include <numeric>
#include <sstream>
namespace ripple {
template<class TIn, class TOut>
class TOffer;
template<class TIn, class TOut>
struct TAmounts;
template<class TIn, class TOut>
class BookStep : public StepImp<TIn, TOut, BookStep<TIn, TOut>>
{
private:
static constexpr uint32_t maxOffersToConsume_ = 2000;
Book book_;
AccountID strandSrc_;
AccountID strandDst_;
beast::Journal j_;
struct Cache
{
TIn in;
TOut out;
Cache () = default;
Cache (TIn const& in_, TOut const& out_)
: in (in_), out (out_)
{
}
};
boost::optional<Cache> cache_;
public:
BookStep (Issue const& in,
Issue const& out,
AccountID const& strandSrc,
AccountID const& strandDst,
beast::Journal j)
: book_ (in, out)
, strandSrc_ (strandSrc)
, strandDst_ (strandDst)
, j_ (j)
{
}
Book const& book() const
{
return book_;
};
boost::optional<EitherAmount>
cachedIn () const override
{
if (!cache_)
return boost::none;
return EitherAmount (cache_->in);
}
boost::optional<EitherAmount>
cachedOut () const override
{
if (!cache_)
return boost::none;
return EitherAmount (cache_->out);
}
std::pair<TIn, TOut>
revImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
TOut const& out);
std::pair<TIn, TOut>
fwdImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
TIn const& in);
std::pair<bool, EitherAmount>
validFwd (
PaymentSandbox& sb,
ApplyView& afView,
EitherAmount const& in) override;
// Check for errors frozen constraints.
TER check(StrandContext const& ctx) const;
private:
friend bool operator==(BookStep const& lhs, BookStep const& rhs)
{
return lhs.book_ == rhs.book_;
}
friend bool operator!=(BookStep const& lhs, BookStep const& rhs)
{
return ! (lhs == rhs);
}
bool equal (Step const& rhs) const override;
void consumeOffer (PaymentSandbox& sb,
TOffer<TIn, TOut>& offer,
TAmounts<TIn, TOut> const& ofrAmt,
TAmounts<TIn, TOut> const& stepAmt) const;
std::string logString () const override
{
std::ostringstream ostr;
ostr <<
"BookStep" <<
"\ninIss: " << book_.in.account <<
"\noutIss: " << book_.out.account <<
"\ninCur: " << book_.in.currency <<
"\noutCur: " << book_.out.currency;
return ostr.str ();
}
};
template <class TIn, class TOut>
bool BookStep<TIn, TOut>::equal (Step const& rhs) const
{
if (auto bs = dynamic_cast<BookStep<TIn, TOut> const*>(&rhs))
return book_ == bs->book_;
return false;
}
// Adjust the offer amount and step amount subject to the given input limit
template <class TIn, class TOut>
static
void limitStepIn (Quality const& ofrQ,
TAmounts<TIn, TOut>& ofrAmt,
TAmounts<TIn, TOut>& stpAmt,
std::uint32_t transferRateIn,
TIn const& limit)
{
if (limit < stpAmt.in)
{
stpAmt.in = limit;
auto const inLmt = mulRatio (
stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
ofrAmt = ofrQ.ceil_in (ofrAmt, inLmt);
stpAmt.out = ofrAmt.out;
}
}
// Adjust the offer amount and step amount subject to the given output limit
template <class TIn, class TOut>
static
void limitStepOut (Quality const& ofrQ,
TAmounts<TIn, TOut>& ofrAmt,
TAmounts<TIn, TOut>& stpAmt,
std::uint32_t transferRateIn,
TOut const& limit)
{
if (limit < stpAmt.out)
{
stpAmt.out = limit;
ofrAmt = ofrQ.ceil_out (ofrAmt, limit);
stpAmt.in = mulRatio (
ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true);
}
}
/* Iterate through the offers at the best quality in a book.
Unfunded offers and bad offers are skipped (and returned).
TakerGets/Taker pays reflects funding.
callback is called with the offer SLE, taker pays, taker gets.
If callback returns false, don't process any more offers.
Return the unfunded and bad offers and the number of offers consumed.
*/
template <class TAmtIn, class TAmtOut, class Callback>
static
std::pair<std::vector<uint256>, std::uint32_t>
forEachOffer (
PaymentSandbox& sb,
ApplyView& afView,
Book const& book,
AccountID const& src,
AccountID const& dst,
Callback& callback,
std::uint32_t limit,
beast::Journal j)
{
auto transferRate = [&](AccountID const& id)->std::uint32_t
{
if (isXRP (id) || id == src || id == dst)
return QUALITY_ONE;
return rippleTransferRate (sb, id);
};
std::uint32_t const trIn = transferRate (book.in.account);
typename FlowOfferStream<TAmtIn, TAmtOut>::StepCounter counter (limit, j);
FlowOfferStream<TAmtIn, TAmtOut> offers (
sb, afView, book, sb.parentCloseTime (), counter, j);
boost::optional<Quality> ofrQ;
while (offers.step ())
{
auto& offer = offers.tip ();
if (!ofrQ)
ofrQ = offer.quality ();
else if (*ofrQ != offer.quality ())
break;
auto const funds = offers.ownerFunds ();
auto ofrAmt = offer.amount ();
auto stpAmt = make_Amounts (
mulRatio (ofrAmt.in, trIn, QUALITY_ONE, /*roundUp*/ true),
ofrAmt.out);
if (funds < stpAmt.out)
limitStepOut (*ofrQ, ofrAmt, stpAmt, trIn, funds);
if (!callback (offer, ofrAmt, stpAmt, trIn))
break;
}
return {offers.toRemove (), counter.count()};
}
template <class TIn, class TOut>
void BookStep<TIn, TOut>::consumeOffer (
PaymentSandbox& sb,
TOffer<TIn, TOut>& offer,
TAmounts<TIn, TOut> const& ofrAmt,
TAmounts<TIn, TOut> const& stepAmt) const
{
// The offer owner gets the ofrAmt. The difference between ofrAmt and stepAmt
// is a transfer fee that goes to book_.in.account
{
auto const dr = accountSend (sb, book_.in.account, offer.owner (),
toSTAmount (ofrAmt.in, book_.in), j_);
if (dr != tesSUCCESS)
Throw<FlowException> (dr);
}
{
auto const cr = accountSend (sb, offer.owner (), book_.out.account,
toSTAmount (stepAmt.out, book_.out), j_);
if (cr != tesSUCCESS)
Throw<FlowException> (cr);
}
offer.consume (sb, ofrAmt);
}
template<class TCollection>
static
auto sum (TCollection const& col)
{
using TResult = std::decay_t<decltype (*col.begin ())>;
if (col.empty ())
return TResult{beast::zero};
return std::accumulate (col.begin () + 1, col.end (), *col.begin ());
};
template<class TIn, class TOut>
std::pair<TIn, TOut>
BookStep<TIn, TOut>::revImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
TOut const& out)
{
cache_.reset ();
TAmounts<TIn, TOut> result (beast::zero, beast::zero);
auto remainingOut = out;
boost::container::flat_multiset<TIn> savedIns;
savedIns.reserve(64);
boost::container::flat_multiset<TOut> savedOuts;
savedOuts.reserve(64);
/* amt fed will be adjusted by owner funds (and may differ from the offer's
amounts - tho always <=)
Return true to continue to receive offers, false to stop receiving offers.
*/
auto eachOffer =
[&](TOffer<TIn, TOut>& offer,
TAmounts<TIn, TOut> const& ofrAmt,
TAmounts<TIn, TOut> const& stpAmt,
std::uint32_t transferRateIn) mutable -> bool
{
if (remainingOut <= beast::zero)
return false;
if (stpAmt.out <= remainingOut)
{
savedIns.insert(stpAmt.in);
savedOuts.insert(stpAmt.out);
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
remainingOut = out - result.out;
this->consumeOffer (sb, offer, ofrAmt, stpAmt);
// return true b/c even if the payment is satisfied,
// we need to consume the offer
return true;
}
else
{
auto ofrAdjAmt = ofrAmt;
auto stpAdjAmt = stpAmt;
limitStepOut (
offer.quality (), ofrAdjAmt, stpAdjAmt, transferRateIn, remainingOut);
remainingOut = beast::zero;
savedIns.insert (stpAdjAmt.in);
savedOuts.insert (remainingOut);
result.in = sum(savedIns);
result.out = out;
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt);
return false;
}
};
{
auto const r = forEachOffer<TIn, TOut> (
sb, afView, book_,
strandSrc_, strandDst_, eachOffer, maxOffersToConsume_, j_);
std::vector<uint256> toRm = std::move(std::get<0>(r));
std::uint32_t const offersConsumed = std::get<1>(r);
ofrsToRm.reserve (ofrsToRm.size () + toRm.size ());
for (auto& o : toRm)
ofrsToRm.emplace_back (std::move (o));
if (offersConsumed >= maxOffersToConsume_)
{
// Too many iterations, mark this strand as dry
cache_.emplace (beast::zero, beast::zero);
return {beast::zero, beast::zero};
}
}
if (remainingOut < beast::zero)
{
// something went very wrong
assert (0);
cache_.emplace (beast::zero, beast::zero);
return {beast::zero, beast::zero};
}
cache_.emplace (result.in, result.out);
return {result.in, result.out};
}
template<class TIn, class TOut>
std::pair<TIn, TOut>
BookStep<TIn, TOut>::fwdImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
TIn const& in)
{
assert(cache_);
TAmounts<TIn, TOut> result (beast::zero, beast::zero);
auto remainingIn = in;
boost::container::flat_multiset<TIn> savedIns;
savedIns.reserve(64);
boost::container::flat_multiset<TOut> savedOuts;
savedOuts.reserve(64);
// amt fed will be adjusted by owner funds (and may differ from the offer's
// amounts - tho always <=)
auto eachOffer =
[&](TOffer<TIn, TOut>& offer,
TAmounts<TIn, TOut> const& ofrAmt,
TAmounts<TIn, TOut> const& stpAmt,
std::uint32_t transferRateIn) mutable -> bool
{
if (remainingIn <= beast::zero)
return false;
if (stpAmt.in <= remainingIn)
{
savedIns.insert(stpAmt.in);
savedOuts.insert(stpAmt.out);
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
remainingIn = in - result.in;
this->consumeOffer (sb, offer, ofrAmt, stpAmt);
// return true b/c even if the payment is satisfied,
// we need to consume the offer
return true;
}
else
{
auto ofrAdjAmt = ofrAmt;
auto stpAdjAmt = stpAmt;
limitStepIn (
offer.quality (), ofrAdjAmt, stpAdjAmt, transferRateIn, remainingIn);
savedIns.insert (remainingIn);
savedOuts.insert (stpAdjAmt.out);
remainingIn = beast::zero;
result.out = sum (savedOuts);
result.in = in;
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt);
return false;
}
};
{
auto const r = forEachOffer<TIn, TOut> (
sb, afView, book_,
strandSrc_, strandDst_, eachOffer, maxOffersToConsume_, j_);
std::vector<uint256> toRm = std::move(std::get<0>(r));
std::uint32_t const offersConsumed = std::get<1>(r);
ofrsToRm.reserve (ofrsToRm.size () + toRm.size ());
for (auto& o : toRm)
ofrsToRm.emplace_back (std::move (o));
if (offersConsumed >= maxOffersToConsume_)
{
// Too many iterations, mark this strand as dry
cache_.emplace (beast::zero, beast::zero);
return {beast::zero, beast::zero};
}
}
if (remainingIn < beast::zero)
{
// something went very wrong
assert (0);
cache_.emplace (beast::zero, beast::zero);
return {beast::zero, beast::zero};
}
cache_.emplace (result.in, result.out);
return {result.in, result.out};
}
template<class TIn, class TOut>
std::pair<bool, EitherAmount>
BookStep<TIn, TOut>::validFwd (
PaymentSandbox& sb,
ApplyView& afView,
EitherAmount const& in)
{
if (!cache_)
{
JLOG (j_.trace) << "Expected valid cache in validFwd";
return {false, EitherAmount (TOut (beast::zero))};
}
auto const savCache = *cache_;
try
{
std::vector<uint256> dummy;
fwdImp (sb, afView, dummy, get<TIn> (in)); // changes cache
}
catch (FlowException const&)
{
return {false, EitherAmount (TOut (beast::zero))};
}
if (!(checkNear (savCache.in, cache_->in) &&
checkNear (savCache.out, cache_->out)))
{
JLOG (j_.error) <<
"Strand re-execute check failed." <<
" ExpectedIn: " << to_string (savCache.in) <<
" CachedIn: " << to_string (cache_->in) <<
" ExpectedOut: " << to_string (savCache.out) <<
" CachedOut: " << to_string (cache_->out);
return {false, EitherAmount (cache_->out)};
}
return {true, EitherAmount (cache_->out)};
}
template<class TIn, class TOut>
TER
BookStep<TIn, TOut>::check(StrandContext const& ctx) const
{
if (book_.in == book_.out)
{
JLOG (j_.debug) << "BookStep: Book with same in and out issuer " << *this;
return temBAD_PATH;
}
if (!isConsistent (book_.in) || !isConsistent (book_.out))
{
JLOG (j_.debug) << "Book: currency is inconsistent with issuer." << *this;
return temBAD_PATH;
}
if (!ctx.seenBooks.insert (book_).second)
{
JLOG (j_.debug) << "BookStep: loop detected: " << *this;
return temBAD_PATH_LOOP;
}
return tesSUCCESS;
}
//------------------------------------------------------------------------------
namespace test
{
// Needed for testing
template <class TIn, class TOut>
static
bool equalHelper (Step const& step, ripple::Book const& book)
{
if (auto bs = dynamic_cast<BookStep<TIn, TOut> const*> (&step))
return book == bs->book ();
return false;
}
bool bookStepEqual (Step const& step, ripple::Book const& book)
{
bool const inXRP = isXRP (book.in.currency);
bool const outXRP = isXRP (book.out.currency);
if (inXRP && outXRP)
return equalHelper<XRPAmount, XRPAmount> (step, book);
if (inXRP && !outXRP)
return equalHelper<XRPAmount, IOUAmount> (step, book);
if (!inXRP && outXRP)
return equalHelper<IOUAmount, XRPAmount> (step, book);
if (!inXRP && !outXRP)
return equalHelper<IOUAmount, IOUAmount> (step, book);
return false;
}
}
//------------------------------------------------------------------------------
template <class TIn, class TOut>
static
std::pair<TER, std::unique_ptr<Step>>
make_BookStepHelper (
StrandContext const& ctx,
Issue const& in,
Issue const& out)
{
auto r = std::make_unique<BookStep<TIn, TOut>> (
in, out, ctx.strandSrc, ctx.strandDst, ctx.j);
auto ter = r->check (ctx);
if (ter != tesSUCCESS)
return {ter, nullptr};
return {tesSUCCESS, std::move (r)};
}
std::pair<TER, std::unique_ptr<Step>>
make_BookStepII (
StrandContext const& ctx,
Issue const& in,
Issue const& out)
{
return make_BookStepHelper<IOUAmount, IOUAmount> (ctx, in, out);
}
std::pair<TER, std::unique_ptr<Step>>
make_BookStepIX (
StrandContext const& ctx,
Issue const& in)
{
Issue out;
return make_BookStepHelper<IOUAmount, XRPAmount> (ctx, in, out);
}
std::pair<TER, std::unique_ptr<Step>>
make_BookStepXI (
StrandContext const& ctx,
Issue const& out)
{
Issue in;
return make_BookStepHelper<XRPAmount, IOUAmount> (ctx, in, out);
}
} // ripple

View File

@@ -0,0 +1,636 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/paths/Credit.h>
#include <ripple/app/paths/impl/StepChecks.h>
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/PaymentSandbox.h>
#include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/Quality.h>
#include <numeric>
#include <sstream>
namespace ripple {
class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
{
private:
AccountID src_;
AccountID dst_;
Currency currency_;
// Transfer fees are never charged when sending directly to or from an
// issuing account
bool noTransferFee_;
beast::Journal j_;
struct Cache
{
IOUAmount in;
IOUAmount srcToDst;
IOUAmount out;
Cache () = default;
Cache (
IOUAmount const& in_,
IOUAmount const& srcToDst_,
IOUAmount const& out_)
: in(in_)
, srcToDst(srcToDst_)
, out(out_)
{}
};
boost::optional<Cache> cache_;
// Returns srcQOut, dstQIn
std::pair <std::uint32_t, std::uint32_t>
qualities (
PaymentSandbox& sb,
bool srcRedeems) const;
public:
DirectStepI (
AccountID const& src,
AccountID const& dst,
Currency const& c,
bool noTransferFee,
beast::Journal j)
:src_(src)
, dst_(dst)
, currency_ (c)
, noTransferFee_ (noTransferFee)
, j_ (j) {}
AccountID const& src () const
{
return src_;
}
AccountID const& dst () const
{
return dst_;
}
Currency const& currency () const
{
return currency_;
}
boost::optional<EitherAmount> cachedIn () const override
{
if (!cache_)
return boost::none;
return EitherAmount (cache_->in);
}
boost::optional<EitherAmount>
cachedOut () const override
{
if (!cache_)
return boost::none;
return EitherAmount (cache_->out);
}
std::pair<IOUAmount, IOUAmount>
revImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
IOUAmount const& out);
std::pair<IOUAmount, IOUAmount>
fwdImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
IOUAmount const& in);
std::pair<bool, EitherAmount>
validFwd (
PaymentSandbox& sb,
ApplyView& afView,
EitherAmount const& in) override;
// Check for error, existing liquidity, and violations of auth/frozen
// constraints.
TER check (StrandContext const& ctx) const;
void setCacheLimiting (
IOUAmount const& fwdIn,
IOUAmount const& fwdSrcToDst,
IOUAmount const& fwdOut);
friend bool operator==(DirectStepI const& lhs, DirectStepI const& rhs)
{
return lhs.src_ == rhs.src_ &&
lhs.dst_ == rhs.dst_ &&
lhs.currency_ == rhs.currency_ &&
lhs.noTransferFee_ == rhs.noTransferFee_;
}
friend bool operator!=(DirectStepI const& lhs, DirectStepI const& rhs)
{
return ! (lhs == rhs);
}
private:
bool equal (Step const& rhs) const override
{
if (auto ds = dynamic_cast<DirectStepI const*> (&rhs))
{
return *this == *ds;
}
return false;
}
std::string logString () const override
{
std::ostringstream ostr;
ostr <<
"DirectStepI: " <<
"\nSrc: " << src_ <<
"\nDst: " << dst_;
return ostr.str ();
}
};
// Compute the maximum value that can flow from src->dst at
// the best available quality.
// return: first element is max amount that can flow,
// second is if src redeems to dst
static
std::pair<IOUAmount, bool>
maxFlow (
PaymentSandbox const& sb,
AccountID const& src,
AccountID const& dst,
Currency const& cur)
{
auto const srcOwed = creditBalance2 (sb, dst, src, cur);
if (srcOwed.signum () > 0)
return {srcOwed, true};
// srcOwed is negative or zero
return {creditLimit2 (sb, dst, src, cur) + srcOwed, false};
}
std::pair<IOUAmount, IOUAmount>
DirectStepI::revImp (
PaymentSandbox& sb,
ApplyView& /*afView*/,
std::vector<uint256>& /*ofrsToRm*/,
IOUAmount const& out)
{
cache_.reset ();
bool srcRedeems;
IOUAmount maxSrcToDst;
std::tie (maxSrcToDst, srcRedeems) =
maxFlow (sb, src_, dst_, currency_);
std::uint32_t srcQOut, dstQIn;
std::tie (srcQOut, dstQIn) = qualities (sb, srcRedeems);
Issue const srcToDstIss (currency_, srcRedeems ? dst_ : src_);
JLOG (j_.trace) <<
"DirectStepI::rev" <<
" srcRedeems: " << srcRedeems <<
" outReq: " << to_string (out) <<
" maxSrcToDst: " << to_string (maxSrcToDst) <<
" srcQOut: " << srcQOut <<
" dstQIn: " << dstQIn;
if (maxSrcToDst.signum () <= 0)
{
JLOG (j_.trace) << "DirectStepI::rev: dry";
cache_.emplace (
IOUAmount (beast::zero),
IOUAmount (beast::zero),
IOUAmount (beast::zero));
return {beast::zero, beast::zero};
}
IOUAmount const srcToDst = mulRatio (
out, QUALITY_ONE, dstQIn, /*roundUp*/ true);
if (srcToDst <= maxSrcToDst)
{
IOUAmount const in = mulRatio (
srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
cache_.emplace (in, srcToDst, out);
rippleCredit (sb,
src_, dst_, toSTAmount (srcToDst, srcToDstIss),
/*checkIssuer*/ true, j_);
JLOG (j_.trace) <<
"DirectStepI::rev: Non-limiting" <<
" srcRedeems: " << srcRedeems <<
" in: " << to_string (in) <<
" srcToDst: " << to_string (srcToDst) <<
" out: " << to_string (out);
return {in, out};
}
// limiting node
IOUAmount const in = mulRatio (
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
IOUAmount const actualOut = mulRatio (
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
cache_.emplace (in, maxSrcToDst, actualOut);
rippleCredit (sb,
src_, dst_, toSTAmount (maxSrcToDst, srcToDstIss),
/*checkIssuer*/ true, j_);
JLOG (j_.trace) <<
"DirectStepI::rev: Limiting" <<
" srcRedeems: " << srcRedeems <<
" in: " << to_string (in) <<
" srcToDst: " << to_string (maxSrcToDst) <<
" out: " << to_string (out);
return {in, actualOut};
}
// The forward pass should never have more liquidity than the reverse
// pass. But sometime rounding differences cause the forward pass to
// deliver more liquidity. Use the cached values from the reverse pass
// to prevent this.
void
DirectStepI::setCacheLimiting (
IOUAmount const& fwdIn,
IOUAmount const& fwdSrcToDst,
IOUAmount const& fwdOut)
{
if (cache_->in < fwdIn)
{
IOUAmount const smallDiff(1, -9);
auto const diff = fwdIn - cache_->in;
if (diff > smallDiff)
{
if (fwdIn.exponent () != cache_->in.exponent () ||
!cache_->in.mantissa () ||
(double(fwdIn.mantissa ()) /
double(cache_->in.mantissa ())) > 1.01)
{
// Detect large diffs on forward pass so they may be investigated
JLOG (j_.warning)
<< "DirectStepI::fwd: setCacheLimiting"
<< " fwdIn: " << to_string (fwdIn)
<< " cacheIn: " << to_string (cache_->in)
<< " fwdSrcToDst: " << to_string (fwdSrcToDst)
<< " cacheSrcToDst: " << to_string (cache_->srcToDst)
<< " fwdOut: " << to_string (fwdOut)
<< " cacheOut: " << to_string (cache_->out);
cache_.emplace (fwdIn, fwdSrcToDst, fwdOut);
return;
}
}
}
cache_->in = fwdIn;
if (fwdSrcToDst < cache_->srcToDst)
cache_->srcToDst = fwdSrcToDst;
if (fwdOut < cache_->out)
cache_->out = fwdOut;
};
std::pair<IOUAmount, IOUAmount>
DirectStepI::fwdImp (
PaymentSandbox& sb,
ApplyView& /*afView*/,
std::vector<uint256>& /*ofrsToRm*/,
IOUAmount const& in)
{
assert (cache_);
bool srcRedeems;
IOUAmount maxSrcToDst;
std::tie (maxSrcToDst, srcRedeems) =
maxFlow (sb, src_, dst_, currency_);
std::uint32_t srcQOut, dstQIn;
std::tie (srcQOut, dstQIn) = qualities (sb, srcRedeems);
Issue const srcToDstIss (currency_, srcRedeems ? dst_ : src_);
JLOG (j_.trace) <<
"DirectStepI::fwd" <<
" srcRedeems: " << srcRedeems <<
" inReq: " << to_string (in) <<
" maxSrcToDst: " << to_string (maxSrcToDst) <<
" srcQOut: " << srcQOut <<
" dstQIn: " << dstQIn;
if (maxSrcToDst.signum () <= 0)
{
JLOG (j_.trace) << "DirectStepI::fwd: dry";
cache_.emplace (
IOUAmount (beast::zero),
IOUAmount (beast::zero),
IOUAmount (beast::zero));
return {beast::zero, beast::zero};
}
IOUAmount const srcToDst = mulRatio (
in, QUALITY_ONE, srcQOut, /*roundUp*/ false);
if (srcToDst <= maxSrcToDst)
{
IOUAmount const out = mulRatio (
srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
setCacheLimiting (in, srcToDst, out);
rippleCredit (sb,
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
/*checkIssuer*/ true, j_);
JLOG (j_.trace) <<
"DirectStepI::fwd: Non-limiting" <<
" srcRedeems: " << srcRedeems <<
" in: " << to_string (in) <<
" srcToDst: " << to_string (srcToDst) <<
" out: " << to_string (out);
}
else
{
// limiting node
IOUAmount const actualIn = mulRatio (
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
IOUAmount const out = mulRatio (
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
setCacheLimiting (actualIn, maxSrcToDst, out);
rippleCredit (sb,
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
/*checkIssuer*/ true, j_);
JLOG (j_.trace) <<
"DirectStepI::rev: Limiting" <<
" srcRedeems: " << srcRedeems <<
" in: " << to_string (actualIn) <<
" srcToDst: " << to_string (srcToDst) <<
" out: " << to_string (out);
}
return {cache_->in, cache_->out};
}
std::pair<bool, EitherAmount>
DirectStepI::validFwd (
PaymentSandbox& sb,
ApplyView& afView,
EitherAmount const& in)
{
if (!cache_)
{
JLOG (j_.trace) << "Expected valid cache in validFwd";
return {false, EitherAmount (IOUAmount (beast::zero))};
}
auto const savCache = *cache_;
assert (!in.native);
bool srcRedeems;
IOUAmount maxSrcToDst;
std::tie (maxSrcToDst, srcRedeems) =
maxFlow (sb, src_, dst_, currency_);
try
{
std::vector<uint256> dummy;
fwdImp (sb, afView, dummy, in.iou); // changes cache
}
catch (FlowException const&)
{
return {false, EitherAmount (IOUAmount (beast::zero))};
}
if (maxSrcToDst < cache_->srcToDst)
{
JLOG (j_.error) <<
"DirectStepI: Strand re-execute check failed." <<
" Exceeded max src->dst limit" <<
" max src->dst: " << to_string (maxSrcToDst) <<
" actual src->dst: " << to_string (cache_->srcToDst);
return {false, EitherAmount(cache_->out)};
}
if (!(checkNear (savCache.in, cache_->in) &&
checkNear (savCache.out, cache_->out)))
{
JLOG (j_.error) <<
"DirectStepI: Strand re-execute check failed." <<
" ExpectedIn: " << to_string (savCache.in) <<
" CachedIn: " << to_string (cache_->in) <<
" ExpectedOut: " << to_string (savCache.out) <<
" CachedOut: " << to_string (cache_->out);
return {false, EitherAmount (cache_->out)};
}
return {true, EitherAmount (cache_->out)};
}
static
std::uint32_t
quality (
PaymentSandbox const& sb,
AccountID const& src,
AccountID const& dst,
Currency const& currency,
// set true for quality in, false for quality out
bool qin)
{
if (src == dst)
return QUALITY_ONE;
auto const sle = sb.read (
keylet::line (dst, src, currency));
if (!sle)
return QUALITY_ONE;
auto const& field = [&]() -> SF_U32 const&
{
if (dst < src)
{
if (qin)
return sfLowQualityIn;
else
return sfLowQualityOut;
}
else
{
if (qin)
return sfHighQualityIn;
else
return sfHighQualityOut;
}
}();
if (! sle->isFieldPresent (field))
return QUALITY_ONE;
auto const q = (*sle)[field];
if (!q)
return QUALITY_ONE;
return q;
}
// Returns srcQOut, dstQIn
std::pair<std::uint32_t, std::uint32_t>
DirectStepI::qualities (
PaymentSandbox& sb,
bool srcRedeems) const
{
if (srcRedeems)
{
return std::make_pair(
quality (
sb, src_, dst_, currency_,
false),
QUALITY_ONE);
}
else
{
// Charge a transfer rate when issuing, unless this is the first step.
std::uint32_t const srcQOut =
noTransferFee_ ? QUALITY_ONE : rippleTransferRate (sb, src_);
return std::make_pair(
srcQOut,
quality ( // dst quality in
sb, src_, dst_, currency_,
true));
}
}
TER DirectStepI::check (StrandContext const& ctx) const
{
if (!src_ || !dst_)
{
JLOG (j_.debug) << "DirectStepI: specified bad account.";
return temBAD_PATH;
}
{
auto sleSrc = ctx.view.read (keylet::account (src_));
if (!sleSrc)
{
JLOG (j_.warning)
<< "DirectStepI: can't receive IOUs from non-existent issuer: "
<< src_;
return terNO_ACCOUNT;
}
auto sleLine = ctx.view.read (keylet::line (src_, dst_, currency_));
if (!sleLine)
{
JLOG (j_.trace) << "DirectStepI: No credit line. " << *this;
return terNO_LINE;
}
auto const authField = (src_ > dst_) ? lsfHighAuth : lsfLowAuth;
if (((*sleSrc)[sfFlags] & lsfRequireAuth) &&
!((*sleLine)[sfFlags] & authField) &&
(*sleLine)[sfBalance] == zero)
{
JLOG (j_.warning)
<< "DirectStepI: can't receive IOUs from issuer without auth."
<< " src: " << src_;
return terNO_AUTH;
}
}
// pure issue/redeem can't be frozen
if (! (ctx.isLast && ctx.isFirst))
{
auto const ter = checkFreeze (ctx.view, src_, dst_, currency_);
if (ter != tesSUCCESS)
return ter;
}
if (ctx.prevStep)
{
if (auto pds = dynamic_cast<DirectStepI const*> (ctx.prevStep))
{
auto const ter = checkNoRipple (
ctx.view, pds->src (), src_, dst_, currency_, j_);
if (ter != tesSUCCESS)
return ter;
}
}
if (!ctx.seenDirectIssues[0].insert (Issue{currency_, src_}).second ||
!ctx.seenDirectIssues[1].insert (Issue{currency_, dst_}).second)
{
JLOG (j_.debug) << "DirectStepI: loop detected: Index: "
<< ctx.strandSize << ' ' << *this;
return temBAD_PATH_LOOP;
}
{
auto const owed = creditBalance (ctx.view, dst_, src_, currency_);
if (owed <= zero)
{
auto const limit = creditLimit (ctx.view, dst_, src_, currency_);
if (-owed >= limit)
{
JLOG (j_.debug)
<< "DirectStepI: dry: owed: " << owed << " limit: " << limit;
return tecPATH_DRY;
}
}
}
return tesSUCCESS;
}
//------------------------------------------------------------------------------
namespace test
{
// Needed for testing
bool directStepEqual (Step const& step,
AccountID const& src,
AccountID const& dst,
Currency const& currency)
{
if (auto ds = dynamic_cast<DirectStepI const*> (&step))
{
return ds->src () == src && ds->dst () == dst &&
ds->currency () == currency;
}
return false;
}
} // test
//------------------------------------------------------------------------------
std::pair<TER, std::unique_ptr<Step>>
make_DirectStepI (
StrandContext const& ctx,
AccountID const& src,
AccountID const& dst,
Currency const& c)
{
auto r = std::make_unique<DirectStepI> (
src, dst, c, /* noTransferFee */ ctx.isFirst, ctx.j);
auto ter = r->check (ctx);
if (ter != tesSUCCESS)
return {ter, nullptr};
return {tesSUCCESS, std::move (r)};
}
} // ripple

View File

@@ -0,0 +1,502 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/basics/contract.h>
#include <ripple/json/json_writer.h>
#include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/XRPAmount.h>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <numeric>
#include <sstream>
namespace ripple {
// Check equal with tolerance
bool checkNear (IOUAmount const& expected, IOUAmount const& actual)
{
double const ratTol = 0.001;
if (abs (expected.exponent () - actual.exponent ()) > 1)
return false;
if (actual.exponent () < -20)
return true;
auto const a = (expected.exponent () < actual.exponent ())
? expected.mantissa () / 10
: expected.mantissa ();
auto const b = (actual.exponent () < expected.exponent ())
? actual.mantissa () / 10
: actual.mantissa ();
if (a == b)
return true;
double const diff = std::abs (a - b);
auto const r = diff / std::max (std::abs (a), std::abs (b));
return r <= ratTol;
};
bool checkNear (XRPAmount const& expected, XRPAmount const& actual)
{
return expected == actual;
};
static
bool isXRPAccount (STPathElement const& pe)
{
if (pe.getNodeType () != STPathElement::typeAccount)
return false;
return isXRP (pe.getAccountID ());
};
static
std::pair<TER, std::unique_ptr<Step>>
toStep (
StrandContext const& ctx,
STPathElement const* e1,
STPathElement const* e2,
Issue const& curIssue)
{
auto& j = ctx.j;
if (ctx.isFirst && e1->isAccount () &&
(e1->getNodeType () & STPathElement::typeCurrency) &&
isXRP (e1->getCurrency ()))
{
return make_XRPEndpointStep (ctx, e1->getAccountID ());
}
if (ctx.isLast && isXRPAccount (*e1) && e2->isAccount())
return make_XRPEndpointStep (ctx, e2->getAccountID());
if (e1->isAccount() && e2->isAccount())
{
return make_DirectStepI (ctx, e1->getAccountID (),
e2->getAccountID (), curIssue.currency);
}
if (e1->isOffer() && e2->isAccount())
{
// should already be taken care of
JLOG (j.warning)
<< "Found offer/account payment step. Aborting payment strand.";
assert (0);
Throw<FlowException> (tefEXCEPTION, "Found offer/account payment step.");
}
assert ((e2->getNodeType () & STPathElement::typeCurrency) ||
(e2->getNodeType () & STPathElement::typeIssuer));
auto const outCurrency = e2->getNodeType () & STPathElement::typeCurrency
? e2->getCurrency ()
: curIssue.currency;
auto const outIssuer = e2->getNodeType () & STPathElement::typeIssuer
? e2->getIssuerID ()
: curIssue.account;
if (isXRP (curIssue.currency) && isXRP (outCurrency))
{
JLOG (j.warning) << "Found xrp/xrp offer payment step";
return {temBAD_PATH, std::unique_ptr<Step>{}};
}
assert (e2->isOffer ());
if (isXRP (outCurrency))
return make_BookStepIX (ctx, curIssue);
if (isXRP (curIssue.currency))
return make_BookStepXI (ctx, {outCurrency, outIssuer});
return make_BookStepII (ctx, curIssue, {outCurrency, outIssuer});
}
std::pair<TER, Strand>
toStrand (
ReadView const& view,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
boost::optional<Issue> const& sendMaxIssue,
STPath const& path,
beast::Journal j)
{
if (isXRP (src))
{
JLOG (j.debug) << "toStrand with xrpAccount as src";
return {temBAD_PATH, Strand{}};
}
if (isXRP (dst))
{
JLOG (j.debug) << "toStrand with xrpAccount as dst";
return {temBAD_PATH, Strand{}};
}
if (!isConsistent (deliver))
{
JLOG (j.debug) << "toStrand inconsistent deliver issue";
return {temBAD_PATH, Strand{}};
}
if (sendMaxIssue && !isConsistent (*sendMaxIssue))
{
JLOG (j.debug) << "toStrand inconsistent sendMax issue";
return {temBAD_PATH, Strand{}};
}
Issue curIssue = [&]
{
auto& currency =
sendMaxIssue ? sendMaxIssue->currency : deliver.currency;
if (isXRP (currency))
return xrpIssue ();
return Issue{currency, src};
}();
STPathElement const firstNode (
STPathElement::typeAll, src, curIssue.currency, curIssue.account);
boost::optional<STPathElement> sendMaxPE;
if (sendMaxIssue && sendMaxIssue->account != src)
sendMaxPE.emplace (sendMaxIssue->account, boost::none, boost::none);
STPathElement const lastNode (dst, boost::none, boost::none);
auto hasCurrency = [](STPathElement const* pe)
{
return pe->getNodeType () & STPathElement::typeCurrency;
};
boost::optional<STPathElement> deliverOfferNode;
boost::optional<STPathElement> deliverAccountNode;
std::vector<STPathElement const*> pes;
// reserve enough for the path, the implied source, destination,
// sendmax and deliver.
pes.reserve (4 + path.size ());
pes.push_back (&firstNode);
if (sendMaxPE)
pes.push_back (&*sendMaxPE);
for (auto& i : path)
pes.push_back (&i);
auto const lastCurrency =
(*boost::find_if (boost::adaptors::reverse (pes), hasCurrency))->getCurrency ();
if (lastCurrency != deliver.currency)
{
deliverOfferNode.emplace (boost::none, deliver.currency, deliver.account);
pes.push_back (&*deliverOfferNode);
}
if (!((pes.back ()->isAccount() && pes.back ()->getAccountID () == deliver.account) ||
(lastNode.isAccount() && lastNode.getAccountID () == deliver.account)))
{
deliverAccountNode.emplace (deliver.account, boost::none, boost::none);
pes.push_back (&*deliverAccountNode);
}
if (*pes.back() != lastNode)
pes.push_back (&lastNode);
auto const strandSrc = firstNode.getAccountID ();
auto const strandDst = lastNode.getAccountID ();
Strand result;
result.reserve (2 * pes.size ());
/* A strand may not include the same account node more than once
in the same currency. In a direct step, an account will show up
at most twice: once as a src and once as a dst (hence the two element array).
The strandSrc and strandDst will only show up once each.
*/
std::array<boost::container::flat_set<Issue>, 2> seenDirectIssues;
// A strand may not include the same offer book more than once
boost::container::flat_set<Book> seenBooks;
seenDirectIssues[0].reserve (pes.size());
seenDirectIssues[1].reserve (pes.size());
seenBooks.reserve (pes.size());
auto ctx = [&](bool isLast = false)
{
return StrandContext{view, result, strandSrc, strandDst, isLast,
seenDirectIssues, seenBooks, j};
};
for (int i = 0; i < pes.size () - 1; ++i)
{
/* Iterate through the path elements considering them in pairs.
The first element of the pair is `cur` and the second element is
`next`. When an offer is one of the pairs, the step created will be for
`next`. This means when `cur` is an offer and `next` is an
account then no step is created, as a step has already been created for
that offer.
*/
boost::optional<STPathElement> impliedPE;
auto cur = pes[i];
auto next = pes[i + 1];
if (cur->isNone() || next->isNone())
return {temBAD_PATH, Strand{}};
/* If both account and issuer are set, use the account
(and don't insert an implied node for the issuer).
This matches the behavior of the previous generation payment code
*/
if (cur->isAccount())
curIssue.account = cur->getAccountID ();
else if (cur->hasIssuer())
curIssue.account = cur->getIssuerID ();
if (cur->hasCurrency())
curIssue.currency = cur->getCurrency ();
if (cur->isAccount() && next->isAccount())
{
if (!isXRP (curIssue.currency) &&
curIssue.account != cur->getAccountID () &&
curIssue.account != next->getAccountID ())
{
JLOG (j.trace) << "Inserting implied account";
auto msr = make_DirectStepI (ctx(), cur->getAccountID (),
curIssue.account, curIssue.currency);
if (msr.first != tesSUCCESS)
return {msr.first, Strand{}};
result.push_back (std::move (msr.second));
Currency dummy;
impliedPE.emplace (STPathElement::typeAccount,
curIssue.account, dummy, curIssue.account);
cur = &*impliedPE;
}
}
else if (cur->isAccount() && next->isOffer())
{
if (curIssue.account != cur->getAccountID ())
{
JLOG (j.trace) << "Inserting implied account before offer";
auto msr = make_DirectStepI (ctx(), cur->getAccountID (),
curIssue.account, curIssue.currency);
if (msr.first != tesSUCCESS)
return {msr.first, Strand{}};
result.push_back (std::move (msr.second));
Currency dummy;
impliedPE.emplace (STPathElement::typeAccount,
curIssue.account, dummy, curIssue.account);
cur = &*impliedPE;
}
}
else if (cur->isOffer() && next->isAccount())
{
if (curIssue.account != next->getAccountID () &&
!isXRP (next->getAccountID ()))
{
JLOG (j.trace) << "Inserting implied account after offer";
auto msr = make_DirectStepI (ctx(), curIssue.account,
next->getAccountID (), curIssue.currency);
if (msr.first != tesSUCCESS)
return {msr.first, Strand{}};
result.push_back (std::move (msr.second));
}
continue;
}
if (!next->isOffer() &&
next->hasCurrency() && next->getCurrency () != curIssue.currency)
{
auto const& nextCurrency = next->getCurrency ();
auto const& nextIssuer =
next->hasIssuer () ? next->getIssuerID () : curIssue.account;
if (isXRP (curIssue.currency))
{
JLOG (j.trace) << "Inserting implied XI offer";
auto msr = make_BookStepXI (
ctx(), {nextCurrency, nextIssuer});
if (msr.first != tesSUCCESS)
return {msr.first, Strand{}};
result.push_back (std::move (msr.second));
}
else if (isXRP (nextCurrency))
{
JLOG (j.trace) << "Inserting implied IX offer";
auto msr = make_BookStepIX (ctx(), curIssue);
if (msr.first != tesSUCCESS)
return {msr.first, Strand{}};
result.push_back (std::move (msr.second));
}
else
{
JLOG (j.trace) << "Inserting implied II offer";
auto msr = make_BookStepII (
ctx(), curIssue, {nextCurrency, nextIssuer});
if (msr.first != tesSUCCESS)
return {msr.first, Strand{}};
result.push_back (std::move (msr.second));
}
impliedPE.emplace (
boost::none, nextCurrency, nextIssuer);
cur = &*impliedPE;
curIssue.currency = nextCurrency;
curIssue.account = nextIssuer;
}
auto s =
toStep (ctx (/*isLast*/ i == pes.size () - 2), cur, next, curIssue);
if (s.first == tesSUCCESS)
result.emplace_back (std::move (s.second));
else
{
JLOG (j.warning) << "toStep failed";
return {s.first, Strand{}};
}
}
return {tesSUCCESS, std::move (result)};
}
std::pair<TER, std::vector<Strand>>
toStrands (
ReadView const& view,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
boost::optional<Issue> const& sendMax,
STPathSet const& paths,
bool addDefaultPath,
beast::Journal j)
{
std::vector<Strand> result;
result.reserve (1 + paths.size ());
// Insert the strand into result if it is not already part of the vector
auto insert = [&](Strand s)
{
bool const hasStrand = boost::find (result, s) != result.end ();
if (!hasStrand)
result.emplace_back (std::move (s));
};
if (addDefaultPath)
{
auto sp = toStrand (view, src, dst, deliver, sendMax, STPath(), j);
auto const ter = sp.first;
auto& strand = sp.second;
if (ter != tesSUCCESS)
{
JLOG (j.trace) << "failed to add default path";
if (isTemMalformed (ter) || paths.empty ()) {
return {ter, std::vector<Strand>{}};
}
}
else if (strand.empty ())
{
JLOG (j.trace) << "toStrand failed";
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
}
insert(std::move(strand));
}
else if (paths.empty ())
{
JLOG (j.debug)
<< "Flow: Invalid transaction: No paths and direct ripple not allowed.";
return {temRIPPLE_EMPTY, std::vector<Strand>{}};
}
TER lastFailTer = tesSUCCESS;
for (auto const& p : paths)
{
auto sp = toStrand (view, src, dst, deliver, sendMax, p, j);
auto ter = sp.first;
auto& strand = sp.second;
if (ter != tesSUCCESS)
{
lastFailTer = ter;
JLOG (j.trace)
<< "failed to add path: ter: " << ter << "path: " << p.getJson(0);
if (isTemMalformed (ter))
return {ter, std::vector<Strand>{}};
}
else if (strand.empty ())
{
JLOG (j.trace) << "toStrand failed";
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
}
if (ter == tesSUCCESS)
insert(std::move(strand));
}
if (result.empty ())
return {lastFailTer, std::move (result)};
return {tesSUCCESS, std::move (result)};
}
StrandContext::StrandContext (
ReadView const& view_,
std::vector<std::unique_ptr<Step>> const& strand_,
// A strand may not include an inner node that
// replicates the source or destination.
AccountID strandSrc_,
AccountID strandDst_,
bool isLast_,
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
boost::container::flat_set<Book>& seenBooks_,
beast::Journal j_)
: view (view_)
, strandSrc (strandSrc_)
, strandDst (strandDst_)
, isFirst (strand_.empty ())
, isLast (isLast_)
, strandSize (strand_.size ())
, prevStep (!strand_.empty () ? strand_.back ().get ()
: nullptr)
, seenDirectIssues(seenDirectIssues_)
, seenBooks(seenBooks_)
, j (j_)
{
}
template<class InAmt, class OutAmt>
bool
isDirectXrpToXrp(Strand const& strand)
{
return false;
}
template<>
bool
isDirectXrpToXrp<XRPAmount, XRPAmount> (Strand const& strand)
{
return (strand.size () == 2);
}
template
bool
isDirectXrpToXrp<XRPAmount, XRPAmount> (Strand const& strand);
template
bool
isDirectXrpToXrp<XRPAmount, IOUAmount> (Strand const& strand);
template
bool
isDirectXrpToXrp<IOUAmount, XRPAmount> (Strand const& strand);
template
bool
isDirectXrpToXrp<IOUAmount, IOUAmount> (Strand const& strand);
} // ripple

View File

@@ -0,0 +1,95 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2015 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_APP_PATHS_IMPL_STEP_CHECKS_H_INCLUDED
#define RIPPLE_APP_PATHS_IMPL_STEP_CHECKS_H_INCLUDED
#include <ripple/basics/Log.h>
#include <ripple/ledger/ReadView.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/UintTypes.h>
#include <beast/beast/utility/Journal.h>
namespace ripple {
inline
TER
checkFreeze (
ReadView const& view,
AccountID const& src,
AccountID const& dst,
Currency const& currency)
{
assert (src != dst);
// check freeze
if (auto sle = view.read (keylet::account (dst)))
{
if (sle->isFlag (lsfGlobalFreeze))
{
return terNO_LINE;
}
}
if (auto sle = view.read (keylet::line (src, dst, currency)))
{
if (sle->isFlag ((dst > src) ? lsfHighFreeze : lsfLowFreeze))
{
return terNO_LINE;
}
}
return tesSUCCESS;
}
inline
TER
checkNoRipple (
ReadView const& view,
AccountID const& prev,
AccountID const& cur,
// This is the account whose constraints we are checking
AccountID const& next,
Currency const& currency,
beast::Journal j)
{
// fetch the ripple lines into and out of this node
auto sleIn = view.read (keylet::line (prev, cur, currency));
auto sleOut = view.read (keylet::line (cur, next, currency));
if (!sleIn || !sleOut)
return terNO_LINE;
if ((*sleIn)[sfFlags] &
((cur > prev) ? lsfHighNoRipple : lsfLowNoRipple) &&
(*sleOut)[sfFlags] &
((cur > next) ? lsfHighNoRipple : lsfLowNoRipple))
{
JLOG (j.info) << "Path violates noRipple constraint between " << prev
<< ", " << cur << " and " << next;
return terNO_RIPPLE;
}
return tesSUCCESS;
}
}
#endif

View File

@@ -0,0 +1,384 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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_APP_PATHS_IMPL_PAYSTEPS_H_INCLUDED
#define RIPPLE_APP_PATHS_IMPL_PAYSTEPS_H_INCLUDED
#include <ripple/app/paths/impl/AmountSpec.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/TER.h>
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>
namespace ripple {
class PaymentSandbox;
class ReadView;
class ApplyView;
/*
A step in a payment path
There are five concrete step classes:
DirectStepI is an IOU step between accounts
BookStepII is an IOU/IOU offer book
BookStepIX is an IOU/XRP offer book
BookStepXI is an XRP/IOU offer book
XRPEndpointStep is the source or destination account for XRP
Amounts may be transformed through a step in either the forward or the
reverse direction. In the forward direction, the function `fwd` is used to
find the amount the step would output given an input amount. In the reverse
direction, the function `rev` is used to find the amount of input needed to
produce the desired output.
Amounts are always transformed using liquidity with the same quality (quality
is the amount out/amount in). For example, a BookStep may use multiple offers
when executing `fwd` or `rev`, but all those offers will be from the same
quality directory.
A step may not have enough liquidity to transform the entire requested amount. Both
`fwd` and `rev` return a pair of amounts (one for input amount, one for output amount)
that show how much of the requested amount the step was actually able use.
*/
class Step
{
public:
virtual ~Step () = default;
/**
Find the amount we need to put into the step to get the requested out
subject to liquidity limits
@param sb view with the strands state of balances and offers
@param afView view the the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param ofrsToRm offers found unfunded or in an error state are added to this collection
@param out requested step output
@return actual step input and output
*/
virtual
std::pair<EitherAmount, EitherAmount>
rev (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
EitherAmount const& out) = 0;
/**
Find the amount we get out of the step given the input
subject to liquidity limits
@param sb view with the strands state of balances and offers
@param afView view the the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param ofrsToRm offers found unfunded or in an error state are added to this collection
@param in requested step input
@return actual step input and output
*/
virtual
std::pair<EitherAmount, EitherAmount>
fwd (
PaymentSandbox&,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
EitherAmount const& in) = 0;
virtual
boost::optional<EitherAmount>
cachedIn () const = 0;
virtual
boost::optional<EitherAmount>
cachedOut () const = 0;
/**
Check if amount is zero
*/
virtual
bool
dry (EitherAmount const& out) const = 0;
virtual
bool
equalOut (
EitherAmount const& lhs,
EitherAmount const& rhs) const = 0;
/**
Check that the step can correctly execute in the forward direction
@param sb view with the strands state of balances and offers
@param afView view the the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param in requested step input
@return first element is true is step is valid, second element is out amount
*/
virtual
std::pair<bool, EitherAmount>
validFwd (
PaymentSandbox& sb,
ApplyView& afView,
EitherAmount const& in) = 0;
friend bool operator==(Step const& lhs, Step const& rhs)
{
return lhs.equal (rhs);
}
friend bool operator!=(Step const& lhs, Step const& rhs)
{
return ! (lhs == rhs);
}
friend
std::ostream&
operator << (
std::ostream& stream,
Step const& step)
{
stream << step.logString ();
return stream;
}
private:
virtual
std::string
logString () const = 0;
virtual bool equal (Step const& rhs) const = 0;
};
using Strand = std::vector<std::unique_ptr<Step>>;
inline
bool operator==(Strand const& lhs, Strand const& rhs)
{
if (lhs.size () != rhs.size ())
return false;
for (size_t i = 0, e = lhs.size (); i != e; ++i)
if (*lhs[i] != *rhs[i])
return false;
return true;
}
/*
Create a strand for the specified path
@param sb view for trust lines, balances, and attributes like auth and freeze
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param sendMax Optional asset to send.
@param path Liquidity sources to use for this strand of the payment. The path
contains an ordered collection of the offer books to use and
accounts to ripple through.
@param addDefaultPath Determines if the default path should be considered
@param l logs to write journal messages to
@return error code and collection of strands
*/
std::pair<TER, Strand>
toStrand (
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
boost::optional<Issue> const& sendMaxIssue,
STPath const& path,
beast::Journal j);
/**
Create a strand for each specified path (including the default path, if
specified)
@param sb view for trust lines, balances, and attributes like auth and freeze
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param sendMax Optional asset to send.
@param paths Paths to use to fullfill the payment. Each path in the pathset
contains an ordered collection of the offer books to use and
accounts to ripple through.
@param addDefaultPath Determines if the default path should be considered
@param l logs to write journal messages to
@return error code and collection of strands
*/
std::pair<TER, std::vector<Strand>>
toStrands (ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
boost::optional<Issue> const& sendMax,
STPathSet const& paths,
bool addDefaultPath,
beast::Journal j);
template <class TIn, class TOut, class TDerived>
struct StepImp : public Step
{
std::pair<EitherAmount, EitherAmount>
rev (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
EitherAmount const& out) override
{
auto const r =
static_cast<TDerived*> (this)->revImp (sb, afView, ofrsToRm, get<TOut>(out));
return {EitherAmount (r.first), EitherAmount (r.second)};
}
// Given the requested amount to consume, compute the amount produced.
// Return the consumed/produced
std::pair<EitherAmount, EitherAmount>
fwd (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
EitherAmount const& in) override
{
auto const r =
static_cast<TDerived*> (this)->fwdImp (sb, afView, ofrsToRm, get<TIn>(in));
return {EitherAmount (r.first), EitherAmount (r.second)};
}
bool
dry (EitherAmount const& out) const override
{
return get<TOut>(out) == beast::zero;
}
bool
equalOut (
EitherAmount const& lhs,
EitherAmount const& rhs) const override
{
return get<TOut> (lhs) == get <TOut> (rhs);
}
};
// Thrown when unexpected errors occur
class FlowException : public std::runtime_error
{
public:
TER ter;
std::string msg;
FlowException (TER t, std::string const& msg)
: std::runtime_error (msg)
, ter (t)
{
}
explicit
FlowException (TER t)
: std::runtime_error (transHuman (t))
, ter (t)
{
}
};
// Check equal with tolerance
bool checkNear (IOUAmount const& expected, IOUAmount const& actual);
bool checkNear (XRPAmount const& expected, XRPAmount const& actual);
/**
Context needed for error checking
*/
struct StrandContext
{
ReadView const& view;
AccountID const strandSrc;
AccountID const strandDst;
bool const isFirst;
bool const isLast = false;
size_t const strandSize;
// The previous step in the strand. Needed to check the no ripple constraint
Step const * const prevStep = nullptr;
// A strand may not include the same account node more than once
// in the same currency. In a direct step, an account will show up
// at most twice: once as a src and once as a dst (hence the two element array).
// The strandSrc and strandDst will only show up once each.
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues;
// A strand may not include the same offer book more than once
boost::container::flat_set<Book>& seenBooks;
beast::Journal j;
StrandContext (ReadView const& view_,
std::vector<std::unique_ptr<Step>> const& strand_,
// A strand may not include an inner node that
// replicates the source or destination.
AccountID strandSrc_,
AccountID strandDst_,
bool isLast_,
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
boost::container::flat_set<Book>& seenBooks_,
beast::Journal j);
};
namespace test
{
// Needed for testing
bool directStepEqual (Step const& step,
AccountID const& src,
AccountID const& dst,
Currency const& currency);
bool xrpEndpointStepEqual (Step const& step, AccountID const& acc);
bool bookStepEqual (Step const& step, ripple::Book const& book);
}
std::pair<TER, std::unique_ptr<Step>>
make_DirectStepI (
StrandContext const& ctx,
AccountID const& src,
AccountID const& dst,
Currency const& c);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepII (
StrandContext const& ctx,
Issue const& in,
Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepIX (
StrandContext const& ctx,
Issue const& in);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepXI (
StrandContext const& ctx,
Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_XRPEndpointStep (
StrandContext const& ctx,
AccountID const& acc);
template<class InAmt, class OutAmt>
bool
isDirectXrpToXrp(Strand const& strand);
} // ripple
#endif

View File

@@ -0,0 +1,509 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
#define RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
#include <BeastConfig.h>
#include <ripple/app/paths/Credit.h>
#include <ripple/app/paths/Flow.h>
#include <ripple/app/paths/impl/AmountSpec.h>
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/XRPAmount.h>
#include <boost/container/flat_set.hpp>
#include <numeric>
#include <sstream>
namespace ripple {
template<class TInAmt, class TOutAmt>
struct StrandResult
{
TER ter = temUNKNOWN;
TInAmt in = beast::zero;
TOutAmt out = beast::zero;
boost::optional<PaymentSandbox> sandbox;
std::vector<uint256> ofrsToRm; // offers to remove
StrandResult () = default;
StrandResult (TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sandbox_,
std::vector<uint256> ofrsToRm_)
: ter (tesSUCCESS)
, in (in_)
, out (out_)
, sandbox (std::move (sandbox_))
, ofrsToRm (std::move (ofrsToRm_))
{
}
StrandResult (TER ter_, std::vector<uint256> ofrsToRm_)
: ter (ter_), ofrsToRm (std::move (ofrsToRm_))
{
}
};
/*
Request `out` amount from a strand
@param baseView Trust lines and balances
@param strand Steps of Accounts to ripple through and offer books to use
@param maxIn Max amount of input allowed
@param out Amount of output requested from the strand
@param j Journal to write log messages to
@return Actual amount in and out from the strand, errors, offers to remove,
and payment sandbox
*/
template<class TInAmt, class TOutAmt>
StrandResult <TInAmt, TOutAmt>
flow (
PaymentSandbox const& baseView,
Strand const& strand,
boost::optional<TInAmt> const& maxIn,
TOutAmt const& out,
beast::Journal j)
{
using Result = StrandResult<TInAmt, TOutAmt>;
if (strand.empty ())
{
JLOG (j.warning) << "Empty strand passed to Liquidity";
return {};
}
std::vector<uint256> ofrsToRm;
if (isDirectXrpToXrp<TInAmt, TOutAmt> (strand))
{
// The current implementation returns NO_LINE for XRP->XRP transfers.
// Keep this behavior
return {tecNO_LINE, std::move (ofrsToRm)};
}
try
{
std::size_t const s = strand.size ();
std::size_t limitingStep = strand.size ();
boost::optional<PaymentSandbox> sb (&baseView);
// The "all funds" view determines if an offer becomes unfunded or is
// found unfunded
// These are the account balances before the strand executes
boost::optional<PaymentSandbox> afView (&baseView);
EitherAmount limitStepOut;
{
EitherAmount stepOut (out);
for (auto i = s; i--;)
{
auto r = strand[i]->rev (*sb, *afView, ofrsToRm, stepOut);
if (strand[i]->dry (r.second))
{
JLOG (j.trace) << "Strand found dry in rev";
return {tecPATH_DRY, std::move (ofrsToRm)};
}
if (i == 0 && maxIn && *maxIn < get<TInAmt> (r.first))
{
// limiting - exceeded maxIn
// Throw out previous results
sb.emplace (&baseView);
limitingStep = i;
// re-execute the limiting step
r = strand[i]->fwd (
*sb, *afView, ofrsToRm, EitherAmount (*maxIn));
limitStepOut = r.second;
if (strand[i]->dry (r.second) ||
get<TInAmt> (r.first) != get<TInAmt> (*maxIn))
{
// Something is very wrong
// throwing out the sandbox can only increase liquidity
// yet the limiting is still limiting
JLOG (j.fatal) << "Re-executed limiting step failed";
assert (0);
return {telFAILED_PROCESSING, std::move (ofrsToRm)};
}
}
else if (!strand[i]->equalOut (r.second, stepOut))
{
// limiting
// Throw out previous results
sb.emplace (&baseView);
afView.emplace (&baseView);
limitingStep = i;
// re-execute the limiting step
stepOut = r.second;
r = strand[i]->rev (*sb, *afView, ofrsToRm, stepOut);
limitStepOut = r.second;
if (strand[i]->dry (r.second) ||
!strand[i]->equalOut (r.second, stepOut))
{
// Something is very wrong
// throwing out the sandbox can only increase liquidity
// yet the limiting is still limiting
JLOG (j.fatal) << "Re-executed limiting step failed";
assert (0);
return {telFAILED_PROCESSING, std::move (ofrsToRm)};
}
}
// prev node needs to produce what this node wants to consume
stepOut = r.first;
}
}
{
EitherAmount stepIn (limitStepOut);
for (auto i = limitingStep + 1; i < s; ++i)
stepIn = strand[i]->fwd (*sb, *afView, ofrsToRm, stepIn).second;
}
auto const strandIn = *strand.front ()->cachedIn ();
auto const strandOut = *strand.back ()->cachedOut ();
#ifndef NDEBUG
{
// Check that the strand will execute as intended
// Re-executing the strand will change the cached values
PaymentSandbox checkSB (&baseView);
PaymentSandbox checkAfView (&baseView);
EitherAmount stepIn (*strand[0]->cachedIn ());
for (auto i = 0; i < s; ++i)
{
bool valid;
std::tie (valid, stepIn) =
strand[i]->validFwd (checkSB, checkAfView, stepIn);
if (!valid)
{
JLOG (j.error)
<< "Strand re-execute check failed. Step: " << i;
return {telFAILED_PROCESSING, std::move (ofrsToRm)};
}
}
}
#endif
return Result (get<TInAmt> (strandIn), get<TOutAmt> (strandOut),
std::move (*sb), std::move (ofrsToRm));
}
catch (FlowException const& e)
{
return {e.ter, std::move (ofrsToRm)};
}
}
template<class TInAmt, class TOutAmt>
struct FlowResult
{
TInAmt in = beast::zero;
TOutAmt out = beast::zero;
boost::optional<PaymentSandbox> sandbox;
TER ter = temUNKNOWN;
FlowResult () = default;
FlowResult (TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sandbox_)
: in (in_)
, out (out_)
, sandbox (std::move (sandbox_))
, ter (tesSUCCESS)
{
}
FlowResult (TER ter_)
: ter (ter_)
{
}
FlowResult (TER ter_, TInAmt const& in_, TOutAmt const& out_)
: in (in_)
, out (out_)
, ter (ter_)
{
}
};
/* Track the non-dry strands
flow will search the non-dry strands (stored in `cur_`) for the best
available liquidity If flow doesn't use all the liquidity of a strand, that
strand is added to `next_`. The strands in `next_` are searched after the
current best liquidity is used.
*/
class ActiveStrands
{
private:
// Strands to be explored for liquidity
std::vector<Strand const*> cur_;
// Strands that may be explored for liquidity on the next iteration
std::vector<Strand const*> next_;
public:
ActiveStrands (std::vector<Strand> const& strands)
{
cur_.reserve (strands.size ());
next_.reserve (strands.size ());
for (auto& strand : strands)
next_.push_back (&strand);
}
// Start a new iteration in the search for liquidity
// Set the current strands to the strands in `next_`
void
activateNext ()
{
// Swap, don't move, so we keep the reserve in next_
cur_.clear ();
std::swap (cur_, next_);
}
void
push (Strand const* s)
{
next_.push_back (s);
}
auto begin ()
{
return cur_.begin ();
}
auto end ()
{
return cur_.end ();
}
auto begin () const
{
return cur_.begin ();
}
auto end () const
{
return cur_.end ();
}
};
/*
Request `out` amount from a collection of strands
Attempt to fullfill the payment by using liquidity from the strands in order
from least expensive to most expensive
@param baseView Trust lines and balances
@param strands Each strand contains the steps of accounts to ripple through
and offer books to use
@param outReq Amount of output requested from the strand
@param flowParams Constraints and options on the payment
@param logs Logs to write journal messages to
@return Actual amount in and out from the strands, errors, and payment sandbox
*/
template <class TInAmt, class TOutAmt>
FlowResult<TInAmt, TOutAmt>
flow (PaymentSandbox const& baseView,
std::vector<Strand> const& strands,
TOutAmt const& outReq,
bool defaultPaths,
bool partialPayment,
boost::optional<Quality> const& limitQuality,
boost::optional<STAmount> const& sendMaxST,
beast::Journal j)
{
using Result = FlowResult<TInAmt, TOutAmt>;
// Used to track the strand that offers the best quality (output/input ratio)
struct BestStrand
{
TInAmt in;
TOutAmt out;
PaymentSandbox sb;
Strand const& strand;
Quality quality;
BestStrand (TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sb_,
Strand const& strand_,
Quality const& quality_)
: in (in_)
, out (out_)
, sb (std::move (sb_))
, strand (strand_)
, quality (quality_)
{
}
};
std::size_t const maxTries = 1000;
std::size_t curTry = 0;
auto const sendMax = [&sendMaxST]()->boost::optional<TInAmt>
{
if (sendMaxST && *sendMaxST >= beast::zero)
{
return toAmount<TInAmt> (*sendMaxST);
}
return boost::none;
}();
TOutAmt remainingOut (outReq);
boost::optional<TInAmt> remainingIn (sendMax);
PaymentSandbox sb (&baseView);
// non-dry strands
ActiveStrands activeStrands (strands);
// Keeping a running sum of the amount in the order they are processed
// will not give the best precision. Keep a collection so they may be summed
// from smallest to largest
boost::container::flat_multiset<TInAmt> savedIns;
savedIns.reserve(maxTries);
boost::container::flat_multiset<TOutAmt> savedOuts;
savedOuts.reserve(maxTries);
auto sum = [](auto const& col)
{
using TResult = std::decay_t<decltype (*col.begin ())>;
if (col.empty ())
return TResult{beast::zero};
return std::accumulate (col.begin () + 1, col.end (), *col.begin ());
};
while (remainingOut > beast::zero &&
(!remainingIn || *remainingIn > beast::zero))
{
++curTry;
if (curTry >= maxTries)
{
return Result (telFAILED_PROCESSING);
}
activeStrands.activateNext();
std::set<uint256> ofrsToRm;
boost::optional<BestStrand> best;
for (auto strand : activeStrands)
{
auto f = flow<TInAmt, TOutAmt> (
sb, *strand, remainingIn, remainingOut, j);
// rm bad offers even if the strand fails
ofrsToRm.insert (f.ofrsToRm.begin (), f.ofrsToRm.end ());
if (f.ter != tesSUCCESS || f.out == beast::zero)
continue;
assert (f.out <= remainingOut && f.sandbox &&
(!remainingIn || f.in <= *remainingIn));
Quality const q (f.out, f.in);
JLOG (j.trace)
<< "New flow iter (iter, in, out): "
<< curTry-1 << " "
<< to_string(f.in) << " "
<< to_string(f.out);
if (limitQuality && q < *limitQuality)
{
JLOG (j.trace)
<< "Path rejected by limitQuality"
<< " limit: " << *limitQuality
<< " path q: " << q;
continue;
}
activeStrands.push (strand);
if (!best || q > best->quality)
best.emplace (f.in, f.out, std::move (*f.sandbox), *strand, q);
}
bool const shouldBreak = !bool(best);
if (best)
{
savedIns.insert (best->in);
savedOuts.insert (best->out);
remainingOut = outReq - sum (savedOuts);
if (sendMax)
remainingIn = *sendMax - sum (savedIns);
JLOG (j.trace)
<< "Best path: in: " << to_string (best->in)
<< " out: " << to_string (best->out)
<< " remainingOut: " << to_string (remainingOut);
best->sb.apply (sb);
}
else
{
JLOG (j.trace) << "All strands dry.";
}
best.reset (); // view in best must be destroyed before modifying base
// view
for (auto const& o : ofrsToRm)
if (auto ok = sb.peek (keylet::offer (o)))
offerDelete (sb, ok, j);
if (shouldBreak)
break;
}
auto const actualOut = sum (savedOuts);
auto const actualIn = sum (savedIns);
JLOG (j.trace)
<< "Total flow: in: " << to_string (actualIn)
<< " out: " << to_string (actualOut);
if (actualOut != outReq)
{
if (actualOut > outReq)
{
assert (0);
return {tefEXCEPTION};
}
if (!partialPayment)
{
return {tecPATH_PARTIAL, actualIn, actualOut};
}
else if (actualOut == beast::zero)
{
return {tecPATH_DRY};
}
}
return Result (actualIn, actualOut, std::move (sb));
}
} // ripple
#endif

View File

@@ -0,0 +1,289 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/paths/Credit.h>
#include <ripple/app/paths/impl/AmountSpec.h>
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/app/paths/impl/StepChecks.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/PaymentSandbox.h>
#include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/Quality.h>
#include <ripple/protocol/XRPAmount.h>
#include <numeric>
#include <sstream>
namespace ripple {
class XRPEndpointStep : public StepImp<XRPAmount, XRPAmount, XRPEndpointStep>
{
private:
AccountID acc_;
bool isLast_;
beast::Journal j_;
// Since this step will always be an endpoint in a strand
// (either the first or last step) the same cache is used
// for cachedIn and cachedOut and only one will ever be used
boost::optional<XRPAmount> cache_;
boost::optional<EitherAmount>
cached () const
{
if (!cache_)
return boost::none;
return EitherAmount (*cache_);
}
public:
XRPEndpointStep (
AccountID const& acc,
bool isLast,
beast::Journal j)
:acc_(acc)
, isLast_(isLast)
, j_ (j) {}
AccountID const& acc () const
{
return acc_;
};
boost::optional<EitherAmount>
cachedIn () const override
{
return cached ();
}
boost::optional<EitherAmount>
cachedOut () const override
{
return cached ();
}
std::pair<XRPAmount, XRPAmount>
revImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
XRPAmount const& out);
std::pair<XRPAmount, XRPAmount>
fwdImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
XRPAmount const& in);
std::pair<bool, EitherAmount>
validFwd (
PaymentSandbox& sb,
ApplyView& afView,
EitherAmount const& in) override;
// Check for errors and violations of frozen constraints.
TER check (StrandContext const& ctx) const;
private:
friend bool operator==(XRPEndpointStep const& lhs, XRPEndpointStep const& rhs);
friend bool operator!=(XRPEndpointStep const& lhs, XRPEndpointStep const& rhs)
{
return ! (lhs == rhs);
}
bool equal (Step const& rhs) const override
{
if (auto ds = dynamic_cast<XRPEndpointStep const*> (&rhs))
{
return *this == *ds;
}
return false;
}
std::string logString () const override
{
std::ostringstream ostr;
ostr <<
"XRPEndpointStep: " <<
"\nAcc: " << acc_;
return ostr.str ();
}
};
inline bool operator==(XRPEndpointStep const& lhs, XRPEndpointStep const& rhs)
{
return lhs.acc_ == rhs.acc_ && lhs.isLast_ == rhs.isLast_;
}
static
XRPAmount
xrpLiquid (ReadView& sb, AccountID const& src)
{
if (auto sle = sb.read (keylet::account (src)))
{
auto const reserve = sb.fees ().accountReserve ((*sle)[sfOwnerCount]);
auto const balance = (*sle)[sfBalance].xrp ();
if (balance < reserve)
return XRPAmount (beast::zero);
return balance - reserve;
}
return XRPAmount (beast::zero);
}
std::pair<XRPAmount, XRPAmount>
XRPEndpointStep::revImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
XRPAmount const& out)
{
auto const balance = xrpLiquid (sb, acc_);
auto const result = isLast_ ? out : std::min (balance, out);
auto& sender = isLast_ ? xrpAccount() : acc_;
auto& receiver = isLast_ ? acc_ : xrpAccount();
auto ter = accountSend (sb, sender, receiver, toSTAmount (result), j_);
if (ter != tesSUCCESS)
return {XRPAmount{beast::zero}, XRPAmount{beast::zero}};
cache_.emplace (result);
return {result, result};
}
std::pair<XRPAmount, XRPAmount>
XRPEndpointStep::fwdImp (
PaymentSandbox& sb,
ApplyView& afView,
std::vector<uint256>& ofrsToRm,
XRPAmount const& in)
{
assert (cache_);
auto const balance = xrpLiquid (sb, acc_);
auto const result = isLast_ ? in : std::min (balance, in);
auto& sender = isLast_ ? xrpAccount() : acc_;
auto& receiver = isLast_ ? acc_ : xrpAccount();
auto ter = accountSend (sb, sender, receiver, toSTAmount (result), j_);
if (ter != tesSUCCESS)
return {XRPAmount{beast::zero}, XRPAmount{beast::zero}};
cache_.emplace (result);
return {result, result};
}
std::pair<bool, EitherAmount>
XRPEndpointStep::validFwd (
PaymentSandbox& sb,
ApplyView& afView,
EitherAmount const& in)
{
if (!cache_)
{
JLOG (j_.error) << "Expected valid cache in validFwd";
return {false, EitherAmount (XRPAmount (beast::zero))};
}
assert (in.native);
auto const& xrpIn = in.xrp;
auto const balance = xrpLiquid (sb, acc_);
if (!isLast_ && balance < xrpIn)
{
JLOG (j_.error) << "XRPEndpointStep: Strand re-execute check failed."
<< " Insufficient balance: " << to_string (balance)
<< " Requested: " << to_string (xrpIn);
return {false, EitherAmount (balance)};
}
if (xrpIn != *cache_)
{
JLOG (j_.error) << "XRPEndpointStep: Strand re-execute check failed."
<< " ExpectedIn: " << to_string (*cache_)
<< " CachedIn: " << to_string (xrpIn);
}
return {true, in};
}
TER
XRPEndpointStep::check (StrandContext const& ctx) const
{
if (!acc_)
{
JLOG (j_.debug) << "XRPEndpointStep: specified bad account.";
return temBAD_PATH;
}
auto sleAcc = ctx.view.read (keylet::account (acc_));
if (!sleAcc)
{
JLOG (j_.warning) << "XRPEndpointStep: can't send or receive XRPs from "
"non-existent account: "
<< acc_;
return terNO_ACCOUNT;
}
if (!ctx.isFirst && !ctx.isLast)
{
return temBAD_PATH;
}
auto& src = isLast_ ? xrpAccount () : acc_;
auto& dst = isLast_ ? acc_ : xrpAccount();
auto ter = checkFreeze (ctx.view, src, dst, xrpCurrency ());
if (ter != tesSUCCESS)
return ter;
return tesSUCCESS;
}
//------------------------------------------------------------------------------
namespace test
{
// Needed for testing
bool xrpEndpointStepEqual (Step const& step, AccountID const& acc)
{
if (auto xs = dynamic_cast<XRPEndpointStep const*> (&step))
{
return xs->acc () == acc;
}
return false;
}
}
//------------------------------------------------------------------------------
std::pair<TER, std::unique_ptr<Step>>
make_XRPEndpointStep (
StrandContext const& ctx,
AccountID const& acc)
{
auto r = std::make_unique<XRPEndpointStep> (acc, ctx.isLast, ctx.j);
auto ter = r->check (ctx);
if (ter != tesSUCCESS)
return {ter, nullptr};
return {tesSUCCESS, std::move (r)};
}
} // ripple

View File

@@ -0,0 +1,692 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/test/jtx.h>
#include <ripple/app/paths/impl/Steps.h>
#include <ripple/basics/contract.h>
#include <ripple/core/Config.h>
#include <ripple/ledger/tests/PathSet.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/JsonFields.h>
namespace ripple {
namespace test {
struct Flow_test;
struct DirectStepInfo
{
AccountID src;
AccountID dst;
Currency currency;
};
struct XRPEndpointStepInfo
{
AccountID acc;
};
enum class TrustFlag {freeze, auth};
/*constexpr*/ std::uint32_t trustFlag (TrustFlag f, bool useHigh)
{
switch(f)
{
case TrustFlag::freeze:
if (useHigh)
return lsfHighFreeze;
return lsfLowFreeze;
case TrustFlag::auth:
if (useHigh)
return lsfHighAuth;
return lsfLowAuth;
}
return 0; // Silence warning about end of non-void function
}
bool getTrustFlag (jtx::Env const& env,
jtx::Account const& src,
jtx::Account const& dst,
Currency const& cur,
TrustFlag flag)
{
if (auto sle = env.le (keylet::line (src, dst, cur)))
{
auto const useHigh = src.id() > dst.id();
return sle->isFlag (trustFlag (flag, useHigh));
}
Throw<std::runtime_error> ("No line in getTrustFlag");
return false; // silence warning
}
jtx::PrettyAmount
xrpMinusFee (jtx::Env const& env, std::int64_t xrpAmount)
{
using namespace jtx;
auto feeDrops = env.current ()->fees ().base;
return drops (
dropsPerXRP<std::int64_t>::value * xrpAmount - feeDrops);
};
bool equal (std::unique_ptr<Step> const& s1,
DirectStepInfo const& dsi)
{
if (!s1)
return false;
return test::directStepEqual (*s1, dsi.src, dsi.dst, dsi.currency);
}
bool equal (std::unique_ptr<Step> const& s1,
XRPEndpointStepInfo const& xrpsi)
{
if (!s1)
return false;
return test::xrpEndpointStepEqual (*s1, xrpsi.acc);
}
bool equal (std::unique_ptr<Step> const& s1, ripple::Book const& bsi)
{
if (!s1)
return false;
return bookStepEqual (*s1, bsi);
}
template <class Iter>
bool strandEqualHelper (Iter i)
{
// base case. all args processed and found equal.
return true;
}
template <class Iter, class StepInfo, class... Args>
bool strandEqualHelper (Iter i, StepInfo&& si, Args&&... args)
{
if (!equal (*i, std::forward<StepInfo> (si)))
return false;
return strandEqualHelper (++i, std::forward<Args> (args)...);
}
template <class... Args>
bool equal (Strand const& strand, Args&&... args)
{
if (strand.size () != sizeof...(Args))
return false;
if (strand.empty ())
return true;
return strandEqualHelper (strand.begin (), std::forward<Args> (args)...);
}
struct Flow_test : public beast::unit_test::suite
{
void testToStrand ()
{
testcase ("To Strand");
using namespace jtx;
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto const carol = Account ("carol");
auto const gw = Account ("gw");
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];
auto const eurC = EUR.currency;
auto const usdC = USD.currency;
using D = DirectStepInfo;
using B = ripple::Book;
using XRPS = XRPEndpointStepInfo;
// Account path element
auto APE = [](AccountID const& a)
{
return STPathElement (
STPathElement::typeAccount, a, xrpCurrency (), xrpAccount ());
};
// Issue path element
auto IPE = [](Issue const& iss)
{
return STPathElement (
STPathElement::typeCurrency | STPathElement::typeIssuer,
xrpAccount (), iss.currency, iss.account);
};
// Currency path element
auto CPE = [](Currency const& c)
{
return STPathElement (
STPathElement::typeCurrency, xrpAccount (), c, xrpAccount ());
};
auto test = [&, this](jtx::Env& env, Issue const& deliver,
boost::optional<Issue> const& sendMaxIssue, STPath const& path,
TER expTer, auto&&... expSteps)
{
auto r = toStrand (*env.current (), alice, bob,
deliver, sendMaxIssue, path, env.app ().logs ().journal ("Flow"));
expect (r.first == expTer);
if (sizeof...(expSteps))
expect (equal (
r.second, std::forward<decltype (expSteps)> (expSteps)...));
};
{
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
test (env, USD, boost::none, STPath(), terNO_LINE);
env.trust (USD (1000), alice, bob, carol);
test (env, USD, boost::none, STPath(), tecPATH_DRY);
env (pay (gw, alice, USD (100)));
env (pay (gw, carol, USD (100)));
// Insert implied account
test (env, USD, boost::none, STPath(), tesSUCCESS,
D{alice, gw, usdC}, D{gw, bob, usdC});
env.trust (EUR (1000), alice, bob);
// Insert implied offer
test (env, EUR, USD.issue (), STPath(), tesSUCCESS,
D{alice, gw, usdC}, B{USD, EUR}, D{gw, bob, eurC});
// Path with explicit offer
test (env, EUR, USD.issue (), STPath ({IPE (EUR)}),
tesSUCCESS, D{alice, gw, usdC}, B{USD, EUR}, D{gw, bob, eurC});
// Path with XRP src currency
test (env, USD, xrpIssue (), STPath ({IPE (USD)}), tesSUCCESS,
XRPS{alice}, B{XRP, USD}, D{gw, bob, usdC});
// Path with XRP dst currency
test (env, xrpIssue(), USD.issue (), STPath ({IPE (XRP)}),
tesSUCCESS, D{alice, gw, usdC}, B{USD, XRP}, XRPS{bob});
// Path with XRP cross currency bridged payment
test (env, EUR, USD.issue (), STPath ({CPE (xrpCurrency ())}),
tesSUCCESS,
D{alice, gw, usdC}, B{USD, XRP}, B{XRP, EUR}, D{gw, bob, eurC});
// XRP -> XRP transaction can't include a path
test (env, XRP, boost::none, STPath ({APE (carol)}), temBAD_PATH);
{
// The root account can't be the src or dst
auto flowJournal = env.app ().logs ().journal ("Flow");
{
// The root account can't be the dst
auto r = toStrand (*env.current (), alice,
xrpAccount (), XRP, USD.issue (), STPath (), flowJournal);
expect (r.first == temBAD_PATH);
}
{
// The root account can't be the src
auto r =
toStrand (*env.current (), xrpAccount (),
alice, XRP, boost::none, STPath (), flowJournal);
expect (r.first == temBAD_PATH);
}
{
// The root account can't be the src
auto r = toStrand (*env.current (),
noAccount (), bob, USD, boost::none, STPath (), flowJournal);
expect (r.first == terNO_ACCOUNT);
}
}
// Create an offer with the same in/out issue
test (env, EUR, USD.issue (), STPath ({IPE (USD), IPE (EUR)}),
temBAD_PATH);
// Path element with type zero
test (env, USD, boost::none,
STPath ({STPathElement (
0, xrpAccount (), xrpCurrency (), xrpAccount ())}),
temBAD_PATH);
// The same account can't appear more than once on a path
// `gw` will be used from alice->carol and implied between carol
// and bob
test (env, USD, boost::none, STPath ({APE (gw), APE (carol)}),
temBAD_PATH_LOOP);
// The same offer can't appear more than once on a path
test (env, EUR, USD.issue (), STPath ({IPE (EUR), IPE (USD), IPE (EUR)}),
temBAD_PATH_LOOP);
}
{
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, noripple (gw));
env.trust (USD (1000), alice, bob);
env (pay (gw, alice, USD (100)));
test (env, USD, boost::none, STPath (), terNO_RIPPLE);
}
{
// check global freeze
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, gw);
env.trust (USD (1000), alice, bob);
env (pay (gw, alice, USD (100)));
// Account can still issue payments
env(fset(alice, asfGlobalFreeze));
test (env, USD, boost::none, STPath (), tesSUCCESS);
env(fclear(alice, asfGlobalFreeze));
test (env, USD, boost::none, STPath (), tesSUCCESS);
// Account can not issue funds
env(fset(gw, asfGlobalFreeze));
test (env, USD, boost::none, STPath (), terNO_LINE);
env(fclear(gw, asfGlobalFreeze));
test (env, USD, boost::none, STPath (), tesSUCCESS);
// Account can not receive funds
env(fset(bob, asfGlobalFreeze));
test (env, USD, boost::none, STPath (), terNO_LINE);
env(fclear(bob, asfGlobalFreeze));
test (env, USD, boost::none, STPath (), tesSUCCESS);
}
{
// Freeze between gw and alice
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, gw);
env.trust (USD (1000), alice, bob);
env (pay (gw, alice, USD (100)));
test (env, USD, boost::none, STPath (), tesSUCCESS);
env (trust (gw, alice["USD"] (0), tfSetFreeze));
expect (getTrustFlag (env, gw, alice, usdC, TrustFlag::freeze));
test (env, USD, boost::none, STPath (), terNO_LINE);
}
{
// check no auth
// An account may require authorization to receive IOUs from an
// issuer
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, gw);
env (fset (gw, asfRequireAuth));
env.trust (USD (1000), alice, bob);
// Authorize alice but not bob
env (trust (gw, alice ["USD"] (1000), tfSetfAuth));
expect (getTrustFlag (env, gw, alice, usdC, TrustFlag::auth));
env (pay (gw, alice, USD (100)));
env.require (balance (alice, USD (100)));
test (env, USD, boost::none, STPath (), terNO_AUTH);
// Check pure issue redeem still works
auto r = toStrand (*env.current (), alice, gw, USD,
boost::none, STPath (), env.app ().logs ().journal ("Flow"));
expect (r.first == tesSUCCESS);
expect (equal (r.second, D{alice, gw, usdC}));
}
}
void testDirectStep ()
{
testcase ("Direct Step");
using namespace jtx;
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto const carol = Account ("carol");
auto const dan = Account ("dan");
auto const erin = Account ("erin");
auto const USDA = alice["USD"];
auto const USDB = bob["USD"];
auto const USDC = carol["USD"];
auto const USDD = dan["USD"];
auto const gw = Account ("gw");
auto const USD = gw["USD"];
{
// Pay USD, trivial path
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, gw);
env.trust (USD (1000), alice, bob);
env (pay (gw, alice, USD (100)));
env (pay (alice, bob, USD (10)), paths (USD));
env.require (balance (bob, USD (10)));
}
{
// XRP transfer
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob);
env (pay (alice, bob, XRP (100)));
env.require (balance (bob, XRP (10000 + 100)));
env.require (balance (alice, xrpMinusFee (env, 10000 - 100)));
}
{
// Partial payments
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, gw);
env.trust (USD (1000), alice, bob);
env (pay (gw, alice, USD (100)));
env (pay (alice, bob, USD (110)), paths (USD),
ter (tecPATH_PARTIAL));
env.require (balance (bob, USD (0)));
env (pay (alice, bob, USD (110)), paths (USD),
txflags (tfPartialPayment));
env.require (balance (bob, USD (100)));
}
{
// Pay by rippling through accounts, use path finder
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, dan);
env.trust (USDA (10), bob);
env.trust (USDB (10), carol);
env.trust (USDC (10), dan);
env (pay (alice, dan, USDC (10)), paths (USDA));
env.require (
balance (bob, USDA (10)),
balance (carol, USDB (10)),
balance (dan, USDC (10)));
}
{
// Pay by rippling through accounts, specify path
// and charge a transfer fee
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, dan);
env.trust (USDA (10), bob);
env.trust (USDB (10), carol);
env.trust (USDC (10), dan);
env (rate (bob, 1.1));
env (pay (alice, dan, USDC (5)), path (bob, carol),
sendmax (USDA (6)), txflags (tfNoRippleDirect));
env.require (balance (dan, USDC (5)));
env.require (balance (bob, USDA (5.5)));
}
{
// test best quality path is taken
// Paths: A->B->D->E ; A->C->D->E
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, dan, erin);
env.trust (USDA (10), bob, carol);
env.trust (USDB (10), dan);
env.trust (USDC (10), dan);
env.trust (USDD (20), erin);
env (rate (bob, 1));
env (rate (carol, 1.1));
env (pay (alice, erin, USDD (5)), path (carol, dan),
path (bob, dan), txflags (tfNoRippleDirect));
env.require (balance (erin, USDD (5)));
env.require (balance (dan, USDB (5)));
env.require (balance (dan, USDC (0)));
}
{
// Limit quality
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol);
env.trust (USDA (10), bob);
env.trust (USDB (10), carol);
env (pay (alice, carol, USDB (5)), sendmax (USDA (4)),
txflags (tfLimitQuality | tfPartialPayment), ter (tecPATH_DRY));
env.require (balance (carol, USDB (0)));
env (pay (alice, carol, USDB (5)), sendmax (USDA (4)),
txflags (tfPartialPayment));
env.require (balance (carol, USDB (4)));
}
}
void testBookStep ()
{
testcase ("Book Step");
using namespace jtx;
auto const gw = Account ("gateway");
auto const USD = gw["USD"];
auto const BTC = gw["BTC"];
auto const EUR = gw["EUR"];
Account const alice ("alice");
Account const bob ("bob");
Account const carol ("carol");
{
// simple IOU/IOU offer
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (BTC (1000), alice, bob, carol);
env (pay (gw, alice, BTC (50)));
env (pay (gw, bob, USD (50)));
env (offer (bob, BTC (50), USD (50)));
env (pay (alice, carol, USD (50)), path (~USD), sendmax (BTC (50)));
env.require (balance (alice, BTC (0)));
env.require (balance (bob, BTC (50)));
env.require (balance (bob, USD (0)));
env.require (balance (carol, USD (50)));
expect (!isOffer (env, bob, BTC (50), USD (50)));
}
{
// simple IOU/XRP XRP/IOU offer
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (BTC (1000), alice, bob, carol);
env (pay (gw, alice, BTC (50)));
env (pay (gw, bob, USD (50)));
env (offer (bob, BTC (50), XRP (50)));
env (offer (bob, XRP (50), USD (50)));
env (pay (alice, carol, USD (50)), path (~XRP, ~USD),
sendmax (BTC (50)));
env.require (balance (alice, BTC (0)));
env.require (balance (bob, BTC (50)));
env.require (balance (bob, USD (0)));
env.require (balance (carol, USD (50)));
expect (!isOffer (env, bob, XRP (50), USD (50)));
expect (!isOffer (env, bob, BTC (50), XRP (50)));
}
{
// simple XRP -> USD through offer and sendmax
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (BTC (1000), alice, bob, carol);
env (pay (gw, bob, USD (50)));
env (offer (bob, XRP (50), USD (50)));
env (pay (alice, carol, USD (50)), path (~USD),
sendmax (XRP (50)));
env.require (balance (alice, xrpMinusFee (env, 10000 - 50)));
env.require (balance (bob, xrpMinusFee (env, 10000 + 50)));
env.require (balance (bob, USD (0)));
env.require (balance (carol, USD (50)));
expect (!isOffer (env, bob, XRP (50), USD (50)));
}
{
// simple USD -> XRP through offer and sendmax
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (BTC (1000), alice, bob, carol);
env (pay (gw, alice, USD (50)));
env (offer (bob, USD (50), XRP (50)));
env (pay (alice, carol, XRP (50)), path (~XRP),
sendmax (USD (50)));
env.require (balance (alice, USD (0)));
env.require (balance (bob, xrpMinusFee (env, 10000 - 50)));
env.require (balance (bob, USD (50)));
env.require (balance (carol, XRP (10000 + 50)));
expect (!isOffer (env, bob, USD (50), XRP (50)));
}
{
// test unfunded offers are removed
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (BTC (1000), alice, bob, carol);
env.trust (EUR (1000), alice, bob, carol);
env (pay (gw, alice, BTC (60)));
env (pay (gw, bob, USD (50)));
env (pay (gw, bob, EUR (50)));
env (offer (bob, BTC (50), USD (50)));
env (offer (bob, BTC (60), EUR (50)));
env (offer (bob, EUR (50), USD (50)));
// unfund offer
env (pay (bob, gw, EUR (50)));
expect (isOffer (env, bob, BTC (50), USD (50)));
expect (isOffer (env, bob, BTC (60), EUR (50)));
expect (isOffer (env, bob, EUR (50), USD (50)));
env (pay (alice, carol, USD (50)),
path (~USD), path (~EUR, ~USD),
sendmax (BTC (60)));
env.require (balance (alice, BTC (10)));
env.require (balance (bob, BTC (50)));
env.require (balance (bob, USD (0)));
env.require (balance (bob, EUR (0)));
env.require (balance (carol, USD (50)));
// used in the payment
expect (!isOffer (env, bob, BTC (50), USD (50)));
// found unfunded
expect (!isOffer (env, bob, BTC (60), EUR (50)));
// unfunded, but should not yet be found unfunded
expect (isOffer (env, bob, EUR (50), USD (50)));
}
}
void testTransferRate ()
{
testcase ("Transfer Rate");
using namespace jtx;
auto const gw = Account ("gateway");
auto const USD = gw["USD"];
auto const BTC = gw["BTC"];
auto const EUR = gw["EUR"];
Account const alice ("alice");
Account const bob ("bob");
Account const carol ("carol");
{
// Simple payment through a gateway with a
// transfer rate
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
env.trust (USD (1000), alice, bob, carol);
env (pay (gw, alice, USD (50)));
env.require (balance (alice, USD (50)));
env (pay (alice, bob, USD (40)), sendmax (USD (50)));
env.require (balance (bob, USD (40)), balance (alice, USD (0)));
}
{
// transfer rate is not charged when issuer is src or dst
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
env.trust (USD (1000), alice, bob, carol);
env (pay (gw, alice, USD (50)));
env.require (balance (alice, USD (50)));
env (pay (alice, gw, USD (40)), sendmax (USD (40)));
env.require (balance (alice, USD (10)));
}
{
// transfer fee on an offer
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
env.trust (USD (1000), alice, bob, carol);
env (pay (gw, bob, USD (50)));
env (offer (bob, XRP (50), USD (50)));
env (pay (alice, carol, USD (40)), path (~USD), sendmax (XRP (50)));
env.require (
balance (alice, xrpMinusFee (env, 10000 - 50)),
balance (bob, USD (0)),
balance (carol, USD (40)));
}
{
// Transfer fee two consecutive offers
Env env (*this, features(featureFlowV2));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
env.trust (USD (1000), alice, bob, carol);
env.trust (EUR (1000), alice, bob, carol);
env (pay (gw, bob, USD (50)));
env (pay (gw, bob, EUR (50)));
env (offer (bob, XRP (50), USD (50)));
env (offer (bob, USD (50), EUR (50)));
env (pay (alice, carol, EUR (32)), path (~USD, ~EUR), sendmax (XRP (50)));
env.require (
balance (alice, xrpMinusFee (env, 10000 - 50)),
balance (bob, USD (40)),
balance (bob, EUR (50 - 40)),
balance (carol, EUR (32)));
}
}
void run() override
{
testDirectStep ();
testBookStep ();
testTransferRate ();
testToStrand ();
}
};
BEAST_DEFINE_TESTSUITE(Flow,app,ripple);
} // test
} // ripple

View File

@@ -395,6 +395,19 @@ public:
expect(equal(sa, Account("alice")["USD"](5))); expect(equal(sa, Account("alice")["USD"](5)));
} }
void
xrp_to_xrp()
{
using namespace jtx;
testcase("XRP to XRP");
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
auto const result = find_paths(env,
"alice", "bob", XRP(5));
expect(std::get<0>(result).empty());
}
void void
path_find_consume_all() path_find_consume_all()
{ {
@@ -845,6 +858,7 @@ public:
quality_paths_quality_set_and_test(); quality_paths_quality_set_and_test();
trust_auto_clear_trust_normal_clear(); trust_auto_clear_trust_normal_clear();
trust_auto_clear_trust_auto_clear(); trust_auto_clear_trust_auto_clear();
xrp_to_xrp();
} }
}; };

View File

@@ -32,14 +32,13 @@ BookTip::BookTip (ApplyView& view, Book const& book)
} }
bool bool
BookTip::step (Logs& l) BookTip::step (beast::Journal j)
{ {
auto viewJ = l.journal ("View");
if (m_valid) if (m_valid)
{ {
if (m_entry) if (m_entry)
{ {
offerDelete (view_, m_entry, viewJ); offerDelete (view_, m_entry, j);
m_entry = nullptr; m_entry = nullptr;
} }
} }
@@ -58,7 +57,7 @@ BookTip::step (Logs& l)
unsigned int di = 0; unsigned int di = 0;
std::shared_ptr<SLE> dir; std::shared_ptr<SLE> dir;
if (dirFirst (view_, *first_page, dir, di, m_index, viewJ)) if (dirFirst (view_, *first_page, dir, di, m_index, j))
{ {
m_dir = dir->key(); m_dir = dir->key();
m_entry = view_.peek(keylet::offer(m_index)); m_entry = view_.peek(keylet::offer(m_index));

View File

@@ -79,7 +79,7 @@ public:
@return `true` if there is a next offer @return `true` if there is a next offer
*/ */
bool bool
step (Logs& l); step (beast::Journal j);
}; };
} }

View File

@@ -344,9 +344,8 @@ CreateOffer::bridged_cross (
// Note the subtle distinction here: self-offers encountered in the // Note the subtle distinction here: self-offers encountered in the
// bridge are taken, but self-offers encountered in the direct book // bridge are taken, but self-offers encountered in the direct book
// are not. // are not.
auto& logs = ctx_.app.logs(); bool have_bridge = offers_leg1.step () && offers_leg2.step ();
bool have_bridge = offers_leg1.step (logs) && offers_leg2.step (logs); bool have_direct = step_account (offers_direct, taker);
bool have_direct = step_account (offers_direct, taker, logs);
int count = 0; int count = 0;
auto viewJ = ctx_.app.journal ("View"); auto viewJ = ctx_.app.journal ("View");
@@ -396,7 +395,7 @@ CreateOffer::bridged_cross (
if (dry_offer (view, offers_direct.tip ())) if (dry_offer (view, offers_direct.tip ()))
{ {
direct_consumed = true; direct_consumed = true;
have_direct = step_account (offers_direct, taker, ctx_.app.logs()); have_direct = step_account (offers_direct, taker);
} }
} }
else else
@@ -433,12 +432,12 @@ CreateOffer::bridged_cross (
if (dry_offer (view, offers_leg1.tip ())) if (dry_offer (view, offers_leg1.tip ()))
{ {
leg1_consumed = true; leg1_consumed = true;
have_bridge = (have_bridge && offers_leg1.step (logs)); have_bridge = (have_bridge && offers_leg1.step ());
} }
if (dry_offer (view, offers_leg2.tip ())) if (dry_offer (view, offers_leg2.tip ()))
{ {
leg2_consumed = true; leg2_consumed = true;
have_bridge = (have_bridge && offers_leg2.step (logs)); have_bridge = (have_bridge && offers_leg2.step ());
} }
} }
@@ -486,7 +485,7 @@ CreateOffer::direct_cross (
TER cross_result (tesSUCCESS); TER cross_result (tesSUCCESS);
int count = 0; int count = 0;
bool have_offer = step_account (offers, taker, ctx_.app.logs()); bool have_offer = step_account (offers, taker);
// Modifying the order or logic of the operations in the loop will cause // Modifying the order or logic of the operations in the loop will cause
// a protocol breaking change. // a protocol breaking change.
@@ -520,7 +519,7 @@ CreateOffer::direct_cross (
if (dry_offer (view, offer)) if (dry_offer (view, offer))
{ {
direct_consumed = true; direct_consumed = true;
have_offer = step_account (offers, taker, ctx_.app.logs()); have_offer = step_account (offers, taker);
} }
if (cross_result != tesSUCCESS) if (cross_result != tesSUCCESS)
@@ -556,9 +555,9 @@ CreateOffer::direct_cross (
// that are from the taker or which cross the taker's threshold. // that are from the taker or which cross the taker's threshold.
// Return false if the is no offer in the book, true otherwise. // Return false if the is no offer in the book, true otherwise.
bool bool
CreateOffer::step_account (OfferStream& stream, Taker const& taker, Logs& logs) CreateOffer::step_account (OfferStream& stream, Taker const& taker)
{ {
while (stream.step (logs)) while (stream.step ())
{ {
auto const& offer = stream.tip (); auto const& offer = stream.tip ();

View File

@@ -99,7 +99,7 @@ private:
// Return false if the is no offer in the book, true otherwise. // Return false if the is no offer in the book, true otherwise.
static static
bool bool
step_account (OfferStream& stream, Taker const& taker, Logs& logs); step_account (OfferStream& stream, Taker const& taker);
// True if the number of offers that have been crossed // True if the number of offers that have been crossed
// exceeds the limit. // exceeds the limit.

View File

@@ -46,7 +46,7 @@ class TOfferBase<STAmount, STAmount>
template<class TIn=STAmount, class TOut=STAmount> template<class TIn=STAmount, class TOut=STAmount>
class TOffer class TOffer
: public TOfferBase<TIn, TOut> : private TOfferBase<TIn, TOut>
{ {
private: private:
SLE::pointer m_entry; SLE::pointer m_entry;

View File

@@ -81,7 +81,7 @@ static
STAmount accountFundsHelper (ReadView const& view, STAmount accountFundsHelper (ReadView const& view,
AccountID const& id, AccountID const& id,
STAmount const& saDefault, STAmount const& saDefault,
Issue const& issue, Issue const&,
FreezeHandling freezeHandling, FreezeHandling freezeHandling,
beast::Journal j) beast::Journal j)
{ {
@@ -118,18 +118,17 @@ XRPAmount accountFundsHelper (ReadView const& view,
template<class TIn, class TOut> template<class TIn, class TOut>
bool bool
TOfferStreamBase<TIn, TOut>::step (Logs& l) TOfferStreamBase<TIn, TOut>::step ()
{ {
// Modifying the order or logic of these // Modifying the order or logic of these
// operations causes a protocol breaking change. // operations causes a protocol breaking change.
auto viewJ = l.journal ("View");
for(;;) for(;;)
{ {
ownerFunds_ = boost::none; ownerFunds_ = boost::none;
// BookTip::step deletes the current offer from the view before // BookTip::step deletes the current offer from the view before
// advancing to the next (unless the ledger entry is missing). // advancing to the next (unless the ledger entry is missing).
if (! tip_.step(l)) if (! tip_.step(j_))
return false; return false;
std::shared_ptr<SLE> entry = tip_.entry(); std::shared_ptr<SLE> entry = tip_.entry();
@@ -154,7 +153,7 @@ TOfferStreamBase<TIn, TOut>::step (Logs& l)
{ {
JLOG(j_.trace) << JLOG(j_.trace) <<
"Removing expired offer " << entry->getIndex(); "Removing expired offer " << entry->getIndex();
permRmOffer (entry, viewJ); permRmOffer (entry);
continue; continue;
} }
@@ -167,14 +166,14 @@ TOfferStreamBase<TIn, TOut>::step (Logs& l)
{ {
JLOG(j_.warning) << JLOG(j_.warning) <<
"Removing bad offer " << entry->getIndex(); "Removing bad offer " << entry->getIndex();
permRmOffer (entry, viewJ); permRmOffer (entry);
offer_ = TOffer<TIn, TOut>{}; offer_ = TOffer<TIn, TOut>{};
continue; continue;
} }
// Calculate owner funds // Calculate owner funds
ownerFunds_ = accountFundsHelper (view_, offer_.owner (), amount.out, ownerFunds_ = accountFundsHelper (view_, offer_.owner (), amount.out,
offer_.issueOut (), fhZERO_IF_FROZEN, viewJ); offer_.issueOut (), fhZERO_IF_FROZEN, j_);
// Check for unfunded offer // Check for unfunded offer
if (*ownerFunds_ <= zero) if (*ownerFunds_ <= zero)
@@ -184,11 +183,11 @@ TOfferStreamBase<TIn, TOut>::step (Logs& l)
// offer is "found unfunded" versus "became unfunded" // offer is "found unfunded" versus "became unfunded"
auto const original_funds = auto const original_funds =
accountFundsHelper (cancelView_, offer_.owner (), amount.out, accountFundsHelper (cancelView_, offer_.owner (), amount.out,
offer_.issueOut (), fhZERO_IF_FROZEN, viewJ); offer_.issueOut (), fhZERO_IF_FROZEN, j_);
if (original_funds == *ownerFunds_) if (original_funds == *ownerFunds_)
{ {
permRmOffer (entry, viewJ); permRmOffer (entry);
JLOG(j_.trace) << JLOG(j_.trace) <<
"Removing unfunded offer " << entry->getIndex(); "Removing unfunded offer " << entry->getIndex();
} }
@@ -208,14 +207,14 @@ TOfferStreamBase<TIn, TOut>::step (Logs& l)
} }
void void
OfferStream::permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) OfferStream::permRmOffer (std::shared_ptr<SLE> const& sle)
{ {
offerDelete (cancelView_, offerDelete (cancelView_,
cancelView_.peek(keylet::offer(sle->key())), j); cancelView_.peek(keylet::offer(sle->key())), j_);
} }
template<class TIn, class TOut> template<class TIn, class TOut>
void FlowOfferStream<TIn, TOut>::permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal) void FlowOfferStream<TIn, TOut>::permRmOffer (std::shared_ptr<SLE> const& sle)
{ {
toRemove_.push_back (sle->key()); toRemove_.push_back (sle->key());
} }

View File

@@ -29,7 +29,7 @@
namespace ripple { namespace ripple {
template<class TIn=STAmount, class TOut=STAmount> template<class TIn, class TOut>
class TOfferStreamBase class TOfferStreamBase
{ {
public: public:
@@ -59,6 +59,10 @@ public:
count_++; count_++;
return true; return true;
} }
std::uint32_t count() const
{
return count_;
};
}; };
protected: protected:
@@ -77,12 +81,14 @@ protected:
virtual virtual
void void
permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) = 0; permRmOffer (std::shared_ptr<SLE> const& sle) = 0;
public: public:
TOfferStreamBase (ApplyView& view, ApplyView& cancelView, TOfferStreamBase (ApplyView& view, ApplyView& cancelView,
Book const& book, NetClock::time_point when, Book const& book, NetClock::time_point when,
StepCounter& counter, beast::Journal journal); StepCounter& counter, beast::Journal journal);
virtual ~TOfferStreamBase() = default;
/** Returns the offer at the tip of the order book. /** Returns the offer at the tip of the order book.
Offers are always presented in decreasing quality. Offers are always presented in decreasing quality.
Only valid if step() returned `true`. Only valid if step() returned `true`.
@@ -101,7 +107,7 @@ public:
@return `true` if there is a valid offer. @return `true` if there is a valid offer.
*/ */
bool bool
step (Logs& l); step ();
TOut ownerFunds () const TOut ownerFunds () const
{ {
@@ -126,14 +132,13 @@ public:
When an offer is removed, it is removed from both views. This grooms the When an offer is removed, it is removed from both views. This grooms the
order book regardless of whether or not the transaction is successful. order book regardless of whether or not the transaction is successful.
*/ */
class OfferStream : public TOfferStreamBase<> class OfferStream : public TOfferStreamBase<STAmount, STAmount>
{ {
protected: protected:
virtual
void void
permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) override; permRmOffer (std::shared_ptr<SLE> const& sle) override;
public: public:
using TOfferStreamBase<>::TOfferStreamBase; using TOfferStreamBase<STAmount, STAmount>::TOfferStreamBase;
}; };
/** Presents and consumes the offers in an order book. /** Presents and consumes the offers in an order book.
@@ -159,9 +164,8 @@ class FlowOfferStream : public TOfferStreamBase<TIn, TOut>
private: private:
std::vector<uint256> toRemove_; std::vector<uint256> toRemove_;
protected: protected:
virtual
void void
permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) override; permRmOffer (std::shared_ptr<SLE> const& sle) override;
public: public:
using TOfferStreamBase<TIn, TOut>::TOfferStreamBase; using TOfferStreamBase<TIn, TOut>::TOfferStreamBase;

View File

@@ -21,7 +21,9 @@
#include <ripple/app/tx/impl/Payment.h> #include <ripple/app/tx/impl/Payment.h>
#include <ripple/app/paths/RippleCalc.h> #include <ripple/app/paths/RippleCalc.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/st.h> #include <ripple/protocol/st.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
namespace ripple { namespace ripple {
@@ -347,12 +349,13 @@ Payment::doApply ()
rcInput.partialPaymentAllowed = partialPaymentAllowed; rcInput.partialPaymentAllowed = partialPaymentAllowed;
rcInput.defaultPathsAllowed = defaultPathsAllowed; rcInput.defaultPathsAllowed = defaultPathsAllowed;
rcInput.limitQuality = limitQuality; rcInput.limitQuality = limitQuality;
rcInput.deleteUnfundedOffers = true;
rcInput.isLedgerOpen = view().open(); rcInput.isLedgerOpen = view().open();
path::RippleCalc::Output rc; path::RippleCalc::Output rc;
{ {
PaymentSandbox pv(&view()); PaymentSandbox pv(&view());
JLOG(j_.debug)
<< "Entering RippleCalc in payment: " << ctx_.tx.getTransactionID();
rc = path::RippleCalc::rippleCalculate ( rc = path::RippleCalc::rippleCalculate (
pv, pv,
maxSourceAmount, maxSourceAmount,
@@ -361,6 +364,7 @@ Payment::doApply ()
account_, account_,
spsPaths, spsPaths,
ctx_.app.logs(), ctx_.app.logs(),
ctx_.app.config(),
&rcInput); &rcInput);
// VFALCO NOTE We might not need to apply, depending // VFALCO NOTE We might not need to apply, depending
// on the TER. But always applying *should* // on the TER. But always applying *should*

View File

@@ -571,6 +571,8 @@ Transactor::operator()()
auto const txID = ctx_.tx.getTransactionID (); auto const txID = ctx_.tx.getTransactionID ();
JLOG(j_.debug) << "Transactor for id: " << txID;
#ifdef BEAST_DEBUG #ifdef BEAST_DEBUG
{ {
Serializer ser; Serializer ser;

View File

@@ -328,6 +328,8 @@ transferXRP (ApplyView& view,
STAmount const& amount, STAmount const& amount,
beast::Journal j); beast::Journal j);
bool flowV2Switchover (NetClock::time_point const closeTime);
} // ripple } // ripple
#endif #endif

View File

@@ -31,6 +31,14 @@
namespace ripple { namespace ripple {
bool flowV2Switchover (NetClock::time_point const closeTime)
{
using namespace std::chrono_literals;
// Mon March 28, 2016 10:00:00am PST
static NetClock::time_point const soTime{512503200s};
return closeTime > soTime;
}
// VFALCO NOTE A copy of the other one for now // VFALCO NOTE A copy of the other one for now
/** Maximum number of entries in a directory page /** Maximum number of entries in a directory page
A change would be protocol-breaking. A change would be protocol-breaking.
@@ -1344,7 +1352,7 @@ rippleTransferFee (ReadView const& view,
// Send regardless of limits. // Send regardless of limits.
// --> saAmount: Amount/currency/issuer to deliver to reciever. // --> saAmount: Amount/currency/issuer to deliver to reciever.
// <-- saActual: Amount actually cost. Sender pay's fees. // <-- saActual: Amount actually cost. Sender pays fees.
static static
TER TER
rippleSend (ApplyView& view, rippleSend (ApplyView& view,
@@ -1352,7 +1360,6 @@ rippleSend (ApplyView& view,
STAmount const& saAmount, STAmount& saActual, beast::Journal j) STAmount const& saAmount, STAmount& saActual, beast::Journal j)
{ {
auto const issuer = saAmount.getIssuer (); auto const issuer = saAmount.getIssuer ();
TER terResult;
assert (!isXRP (uSenderID) && !isXRP (uReceiverID)); assert (!isXRP (uSenderID) && !isXRP (uReceiverID));
assert (uSenderID != uReceiverID); assert (uSenderID != uReceiverID);
@@ -1361,33 +1368,43 @@ rippleSend (ApplyView& view,
{ {
// Direct send: redeeming IOUs and/or sending own IOUs. // Direct send: redeeming IOUs and/or sending own IOUs.
rippleCredit (view, uSenderID, uReceiverID, saAmount, false, j); rippleCredit (view, uSenderID, uReceiverID, saAmount, false, j);
saActual = saAmount; saActual = saAmount;
terResult = tesSUCCESS; return tesSUCCESS;
}
// Sending 3rd party IOUs: transit.
// Calculate the amount to transfer accounting
// for any transfer fees:
if (!flowV2Switchover (view.info ().parentCloseTime))
{
STAmount const saTransitFee = rippleTransferFee (
view, uSenderID, uReceiverID, issuer, saAmount, j);
saActual = !saTransitFee ? saAmount : saAmount + saTransitFee;
saActual.setIssuer (issuer); // XXX Make sure this done in + above.
} }
else else
{ {
// Sending 3rd party IOUs: transit. auto const rate = rippleTransferRate (view, issuer);
if (QUALITY_ONE == rate)
STAmount saTransitFee = rippleTransferFee (view, saActual = saAmount;
uSenderID, uReceiverID, issuer, saAmount, j); else
saActual =
saActual = !saTransitFee ? saAmount : saAmount + saTransitFee; multiply (saAmount, amountFromRate (rate), saAmount.issue ());
saActual.setIssuer (issuer); // XXX Make sure this done in + above.
JLOG (j.debug) << "rippleSend> " <<
to_string (uSenderID) <<
" - > " << to_string (uReceiverID) <<
" : deliver=" << saAmount.getFullText () <<
" fee=" << saTransitFee.getFullText () <<
" cost=" << saActual.getFullText ();
terResult = rippleCredit (view, issuer, uReceiverID, saAmount, true, j);
if (tesSUCCESS == terResult)
terResult = rippleCredit (view, uSenderID, issuer, saActual, true, j);
} }
JLOG (j.debug) << "rippleSend> " <<
to_string (uSenderID) <<
" - > " << to_string (uReceiverID) <<
" : deliver=" << saAmount.getFullText () <<
" cost=" << saActual.getFullText ();
TER terResult = rippleCredit (view, issuer, uReceiverID, saAmount, true, j);
if (tesSUCCESS == terResult)
terResult = rippleCredit (view, uSenderID, issuer, saActual, true, j);
return terResult; return terResult;
} }

View File

@@ -0,0 +1,110 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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_PROTOCOL_AMOUNTCONVERSION_H_INCLUDED
#define RIPPLE_PROTOCOL_AMOUNTCONVERSION_H_INCLUDED
#include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/XRPAmount.h>
#include <ripple/protocol/STAmount.h>
namespace ripple {
inline
STAmount
toSTAmount (IOUAmount const& iou, Issue const& iss)
{
bool const isNeg = iou.signum() < 0;
std::uint64_t const umant = isNeg ? - iou.mantissa () : iou.mantissa ();
return STAmount (iss, umant, iou.exponent (), /*native*/ false, isNeg,
STAmount::unchecked ());
}
inline
STAmount
toSTAmount (IOUAmount const& iou)
{
return toSTAmount (iou, noIssue ());
}
inline
STAmount
toSTAmount (XRPAmount const& xrp)
{
bool const isNeg = xrp.signum() < 0;
std::uint64_t const umant = isNeg ? - xrp.drops () : xrp.drops ();
return STAmount (umant, isNeg);
}
inline
STAmount
toSTAmount (XRPAmount const& xrp, Issue const& iss)
{
assert (isXRP(iss.account) && isXRP(iss.currency));
return toSTAmount (xrp);
}
template <class T>
T
toAmount (STAmount const& amt)
{
static_assert(sizeof(T) == -1, "Must use specialized function");
return T(0);
}
template <>
inline
STAmount
toAmount<STAmount> (STAmount const& amt)
{
return amt;
}
template <>
inline
IOUAmount
toAmount<IOUAmount> (STAmount const& amt)
{
assert (amt.mantissa () < std::numeric_limits<std::int64_t>::max ());
bool const isNeg = amt.negative ();
std::int64_t const sMant =
isNeg ? - std::int64_t (amt.mantissa ()) : amt.mantissa ();
assert (! isXRP (amt));
return IOUAmount (sMant, amt.exponent ());
}
template <>
inline
XRPAmount
toAmount<XRPAmount> (STAmount const& amt)
{
assert (amt.mantissa () < std::numeric_limits<std::int64_t>::max ());
bool const isNeg = amt.negative ();
std::int64_t const sMant =
isNeg ? - std::int64_t (amt.mantissa ()) : amt.mantissa ();
assert (isXRP (amt));
return XRPAmount (sMant);
}
}
#endif

View File

@@ -39,6 +39,7 @@ extern uint256 const featureTickets;
extern uint256 const featureSusPay; extern uint256 const featureSusPay;
extern uint256 const featureTrustSetAuth; extern uint256 const featureTrustSetAuth;
extern uint256 const featureFeeEscalation; extern uint256 const featureFeeEscalation;
extern uint256 const featureFlowV2;
} // ripple } // ripple

View File

@@ -140,6 +140,11 @@ public:
std::string std::string
to_string (IOUAmount const& amount); to_string (IOUAmount const& amount);
/* Return num*amt/den
This function keeps more precision than computing
num*amt, storing the result in an IOUAmount, then
dividing by den.
*/
IOUAmount IOUAmount
mulRatio ( mulRatio (
IOUAmount const& amt, IOUAmount const& amt,

View File

@@ -20,7 +20,7 @@
#ifndef RIPPLE_PROTOCOL_QUALITY_H_INCLUDED #ifndef RIPPLE_PROTOCOL_QUALITY_H_INCLUDED
#define RIPPLE_PROTOCOL_QUALITY_H_INCLUDED #define RIPPLE_PROTOCOL_QUALITY_H_INCLUDED
#include <ripple/protocol/AmountSpec.h> #include <ripple/protocol/AmountConversions.h>
#include <ripple/protocol/IOUAmount.h> #include <ripple/protocol/IOUAmount.h>
#include <ripple/protocol/STAmount.h> #include <ripple/protocol/STAmount.h>
#include <ripple/protocol/XRPAmount.h> #include <ripple/protocol/XRPAmount.h>
@@ -39,7 +39,7 @@ namespace ripple {
For offers, "in" is always TakerPays and "out" is For offers, "in" is always TakerPays and "out" is
always TakerGets. always TakerGets.
*/ */
template<class TIn, class TOut> template<class In, class Out>
struct TAmounts struct TAmounts
{ {
TAmounts() = default; TAmounts() = default;
@@ -50,7 +50,7 @@ struct TAmounts
{ {
} }
TAmounts (TIn const& in_, TOut const& out_) TAmounts (In const& in_, Out const& out_)
: in (in_) : in (in_)
, out (out_) , out (out_)
{ {
@@ -77,32 +77,32 @@ struct TAmounts
return *this; return *this;
} }
TIn in; In in;
TOut out; Out out;
}; };
template<class TIn, class TOut> template<class In, class Out>
TAmounts<TIn, TOut> make_Amounts(TIn const& in, TOut const& out) TAmounts<In, Out> make_Amounts(In const& in, Out const& out)
{ {
return TAmounts<TIn, TOut>(in, out); return TAmounts<In, Out>(in, out);
} }
using Amounts = TAmounts<STAmount, STAmount>; using Amounts = TAmounts<STAmount, STAmount>;
template<class TIn, class TOut> template<class In, class Out>
bool bool
operator== ( operator== (
TAmounts<TIn, TOut> const& lhs, TAmounts<In, Out> const& lhs,
TAmounts<TIn, TOut> const& rhs) noexcept TAmounts<In, Out> const& rhs) noexcept
{ {
return lhs.in == rhs.in && lhs.out == rhs.out; return lhs.in == rhs.in && lhs.out == rhs.out;
} }
template<class TIn, class TOut> template<class In, class Out>
bool bool
operator!= ( operator!= (
TAmounts<TIn, TOut> const& lhs, TAmounts<In, Out> const& lhs,
TAmounts<TIn, TOut> const& rhs) noexcept TAmounts<In, Out> const& rhs) noexcept
{ {
return ! (lhs == rhs); return ! (lhs == rhs);
} }
@@ -139,8 +139,8 @@ public:
Quality (Amounts const& amount); Quality (Amounts const& amount);
/** Create a quality from the ratio of two amounts. */ /** Create a quality from the ratio of two amounts. */
template<class TIn, class TOut> template<class In, class Out>
Quality (TOut const& out, TIn const& in) Quality (Out const& out, In const& in)
: Quality (Amounts (toSTAmount (in), : Quality (Amounts (toSTAmount (in),
toSTAmount (out))) toSTAmount (out)))
{} {}
@@ -177,9 +177,9 @@ public:
Amounts Amounts
ceil_in (Amounts const& amount, STAmount const& limit) const; ceil_in (Amounts const& amount, STAmount const& limit) const;
template<class TIn, class TOut> template<class In, class Out>
TAmounts<TIn, TOut> TAmounts<In, Out>
ceil_in (TAmounts<TIn, TOut> const& amount, TIn const& limit) const ceil_in (TAmounts<In, Out> const& amount, In const& limit) const
{ {
if (amount.in <= limit) if (amount.in <= limit)
return amount; return amount;
@@ -189,7 +189,7 @@ public:
Amounts stAmt (toSTAmount (amount.in), toSTAmount (amount.out)); Amounts stAmt (toSTAmount (amount.in), toSTAmount (amount.out));
STAmount stLim (toSTAmount (limit)); STAmount stLim (toSTAmount (limit));
auto const stRes = ceil_in (stAmt, stLim); auto const stRes = ceil_in (stAmt, stLim);
return TAmounts<TIn, TOut> (toAmount<TIn> (stRes.in), toAmount<TOut> (stRes.out)); return TAmounts<In, Out> (toAmount<In> (stRes.in), toAmount<Out> (stRes.out));
} }
/** Returns the scaled amount with out capped. /** Returns the scaled amount with out capped.
@@ -199,9 +199,9 @@ public:
Amounts Amounts
ceil_out (Amounts const& amount, STAmount const& limit) const; ceil_out (Amounts const& amount, STAmount const& limit) const;
template<class TIn, class TOut> template<class In, class Out>
TAmounts<TIn, TOut> TAmounts<In, Out>
ceil_out (TAmounts<TIn, TOut> const& amount, TOut const& limit) const ceil_out (TAmounts<In, Out> const& amount, Out const& limit) const
{ {
if (amount.out <= limit) if (amount.out <= limit)
return amount; return amount;
@@ -211,7 +211,7 @@ public:
Amounts stAmt (toSTAmount (amount.in), toSTAmount (amount.out)); Amounts stAmt (toSTAmount (amount.in), toSTAmount (amount.out));
STAmount stLim (toSTAmount (limit)); STAmount stLim (toSTAmount (limit));
auto const stRes = ceil_out (stAmt, stLim); auto const stRes = ceil_out (stAmt, stLim);
return TAmounts<TIn, TOut> (toAmount<TIn> (stRes.in), toAmount<TOut> (stRes.out)); return TAmounts<In, Out> (toAmount<In> (stRes.in), toAmount<Out> (stRes.out));
} }
/** Returns `true` if lhs is lower quality than `rhs`. /** Returns `true` if lhs is lower quality than `rhs`.

View File

@@ -135,6 +135,23 @@ public:
return !isOffer (); return !isOffer ();
} }
bool
hasIssuer () const
{
return getNodeType () & STPathElement::typeIssuer;
};
bool
hasCurrency () const
{
return getNodeType () & STPathElement::typeCurrency;
};
bool
isNone () const
{
return getNodeType () == STPathElement::typeNone;
};
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a // Nodes are either an account ID or a offer prefix. Offer prefixs denote a
// class of offers. // class of offers.
AccountID const& AccountID const&

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_PROTOCOL_XRPAMOUNT_H_INCLUDED #ifndef RIPPLE_PROTOCOL_XRPAMOUNT_H_INCLUDED
#define RIPPLE_PROTOCOL_XRPAMOUNT_H_INCLUDED #define RIPPLE_PROTOCOL_XRPAMOUNT_H_INCLUDED
#include <ripple/basics/contract.h>
#include <ripple/protocol/SystemParameters.h> #include <ripple/protocol/SystemParameters.h>
#include <beast/utility/Zero.h> #include <beast/utility/Zero.h>
#include <boost/operators.hpp> #include <boost/operators.hpp>
@@ -144,15 +145,25 @@ mulRatio (
bool roundUp) bool roundUp)
{ {
using namespace boost::multiprecision; using namespace boost::multiprecision;
if (!den)
Throw<std::runtime_error> ("division by zero");
int128_t const den128 (den); int128_t const den128 (den);
int128_t const num128 (num); int128_t const num128 (num);
int128_t const amt128 (amt.drops ()); int128_t const amt128 (amt.drops ());
auto const m = int128_t (amt.drops ()) * num; auto const neg = amt.drops () < 0;
auto const m = amt128 * num;
auto r = m / den; auto r = m / den;
if (roundUp && r >= 0 && (m % den)) if (m % den)
r += 1; {
if (!neg && roundUp)
r += 1;
if (neg && !roundUp)
r -= 1;
}
if (r > std::numeric_limits<std::int64_t>::max ()) if (r > std::numeric_limits<std::int64_t>::max ())
throw std::overflow_error ("XRP mulRatio overflow"); Throw<std::overflow_error> ("XRP mulRatio overflow");
return XRPAmount (r.convert_to<std::int64_t> ()); return XRPAmount (r.convert_to<std::int64_t> ());
} }

View File

@@ -50,4 +50,6 @@ uint256 const featureTickets = feature("Tickets");
uint256 const featureSusPay = feature("SusPay"); uint256 const featureSusPay = feature("SusPay");
uint256 const featureTrustSetAuth = feature("TrustSetAuth"); uint256 const featureTrustSetAuth = feature("TrustSetAuth");
uint256 const featureFeeEscalation = feature("FeeEscalation"); uint256 const featureFeeEscalation = feature("FeeEscalation");
uint256 const featureFlowV2 = feature("FlowV2");
} // ripple } // ripple

View File

@@ -254,7 +254,13 @@ mulRatio (
{ {
using namespace boost::multiprecision; using namespace boost::multiprecision;
static std::vector<uint128_t> const logTable = [] if (!den)
Throw<std::runtime_error> ("division by zero");
// A vector with the value 10^index for indexes from 0 to 29
// The largest intermediate value we expect is 2^96, which
// is less than 10^29
static auto const powerTable = []
{ {
std::vector<uint128_t> result; std::vector<uint128_t> result;
result.reserve (30); // 2^96 is largest intermediate result size result.reserve (30); // 2^96 is largest intermediate result size
@@ -266,26 +272,39 @@ mulRatio (
}; };
return result; return result;
}(); }();
// Return floor(log10(v))
// Note: Returns -1 for v == 0 // Note: Returns -1 for v == 0
static auto log10Floor = [](uint128_t const& v) static auto log10Floor = [](uint128_t const& v)
{ {
auto const l = std::lower_bound (logTable.begin (), logTable.end (), v); // Find the index of the first element >= the requested element, the index
int index = std::distance (logTable.begin (), l); // is the log of the element in the log table.
auto const l = std::lower_bound (powerTable.begin (), powerTable.end (), v);
int index = std::distance (powerTable.begin (), l);
// If we're not equal, subtract to get the floor
if (*l != v) if (*l != v)
--index; --index;
return index; return index;
}; };
// Return ceil(log10(v))
static auto log10Ceil = [](uint128_t const& v) static auto log10Ceil = [](uint128_t const& v)
{ {
auto const l = std::lower_bound (logTable.begin (), logTable.end (), v); // Find the index of the first element >= the requested element, the index
return int(std::distance (logTable.begin (), l)); // is the log of the element in the log table.
auto const l = std::lower_bound (powerTable.begin (), powerTable.end (), v);
return int(std::distance (powerTable.begin (), l));
}; };
static auto const fl64 = static auto const fl64 =
log10Floor (std::numeric_limits<std::int64_t>::max ()); log10Floor (std::numeric_limits<std::int64_t>::max ());
bool const neg = amt.mantissa () < 0; bool const neg = amt.mantissa () < 0;
uint128_t const den128 (den); uint128_t const den128 (den);
// a 32 value * a 64 bit value and stored in a 128 bit value. This will never overflow
uint128_t const mul = uint128_t const mul =
uint128_t (neg ? -amt.mantissa () : amt.mantissa ()) * uint128_t (num); uint128_t (neg ? -amt.mantissa () : amt.mantissa ()) * uint128_t (num);
auto low = mul / den128; auto low = mul / den128;
uint128_t rem (mul - low * den128); uint128_t rem (mul - low * den128);
@@ -293,27 +312,37 @@ mulRatio (
if (rem) if (rem)
{ {
// Mathematically, the result is low + rem/den128. However, since this
// uses integer division rem/den128 will be zero. Scale the result so
// low does not overflow the largest amount we can store in the mantissa
// and (rem/den128) is as large as possible. Scale by multiplying low
// and rem by 10 and subtracting one from the exponent. We could do this
// with a loop, but it's more efficient to use logarithms.
auto const roomToGrow = fl64 - log10Ceil (low); auto const roomToGrow = fl64 - log10Ceil (low);
if (roomToGrow > 0) if (roomToGrow > 0)
{ {
exponent -= roomToGrow; exponent -= roomToGrow;
low *= logTable[roomToGrow]; low *= powerTable[roomToGrow];
rem *= logTable[roomToGrow]; rem *= powerTable[roomToGrow];
} }
auto const addRem = rem / den128; auto const addRem = rem / den128;
low += addRem; low += addRem;
rem = rem - addRem * den128; rem = rem - addRem * den128;
} }
// The largest result we can have is ~2^95, which overflows the 64 bit
// result we can store in the mantissa. Scale result down by dividing by ten
// and adding one to the exponent until the low will fit in the 64-bit
// mantissa. Use logarithms to avoid looping.
bool hasRem = bool(rem); bool hasRem = bool(rem);
auto const mustShrink = log10Ceil (low) - fl64; auto const mustShrink = log10Ceil (low) - fl64;
if (mustShrink > 0) if (mustShrink > 0)
{ {
uint128_t const sav (low); uint128_t const sav (low);
exponent += mustShrink; exponent += mustShrink;
low /= logTable[mustShrink]; low /= powerTable[mustShrink];
if (!hasRem && roundUp) if (!hasRem)
hasRem = bool(sav - low * logTable[mustShrink]); hasRem = bool(sav - low * powerTable[mustShrink]);
} }
std::int64_t mantissa = low.convert_to<std::int64_t> (); std::int64_t mantissa = low.convert_to<std::int64_t> ();
@@ -324,13 +353,28 @@ mulRatio (
IOUAmount result (mantissa, exponent); IOUAmount result (mantissa, exponent);
if (roundUp && hasRem && !neg) if (hasRem)
{ {
if (!result) // handle rounding
if (roundUp && !neg)
{ {
return IOUAmount (minMantissa, minExponent); if (!result)
{
return IOUAmount (minMantissa, minExponent);
}
// This addition cannot overflow because the mantissa is already normalized
return IOUAmount (result.mantissa () + 1, result.exponent ());
}
if (!roundUp && neg)
{
if (!result)
{
return IOUAmount (-minMantissa, minExponent);
}
// This subtraction cannot underflow because `result` is not zero
return IOUAmount (result.mantissa () - 1, result.exponent ());
} }
return IOUAmount (result.mantissa() + 1, result.exponent());
} }
return result; return result;

View File

@@ -160,6 +160,91 @@ public:
expect(to_string(IOUAmount (-2, -20)) == "-2000000000000000e-35"); expect(to_string(IOUAmount (-2, -20)) == "-2000000000000000e-35");
} }
void testMulRatio()
{
testcase ("mulRatio");
/* The range for the mantissa when normalized */
constexpr std::int64_t minMantissa = 1000000000000000ull;
constexpr std::int64_t maxMantissa = 9999999999999999ull;
// log(2,maxMantissa) ~ 53.15
/* The range for the exponent when normalized */
constexpr int minExponent = -96;
constexpr int maxExponent = 80;
constexpr auto maxUInt = std::numeric_limits<std::uint32_t>::max ();
{
// multiply by a number that would overflow the mantissa, then
// divide by the same number, and check we didn't lose any value
IOUAmount bigMan (maxMantissa, 0);
expect (bigMan == mulRatio (bigMan, maxUInt, maxUInt, true));
// rounding mode shouldn't matter as the result is exact
expect (bigMan == mulRatio (bigMan, maxUInt, maxUInt, false));
}
{
// Similar test as above, but for negative values
IOUAmount bigMan (-maxMantissa, 0);
expect (bigMan == mulRatio (bigMan, maxUInt, maxUInt, true));
// rounding mode shouldn't matter as the result is exact
expect (bigMan == mulRatio (bigMan, maxUInt, maxUInt, false));
}
{
// small amounts
IOUAmount tiny (minMantissa, minExponent);
// Round up should give the smallest allowable number
expect (tiny == mulRatio (tiny, 1, maxUInt, true));
expect (tiny == mulRatio (tiny, maxUInt - 1, maxUInt, true));
// rounding down should be zero
expect (beast::zero == mulRatio (tiny, 1, maxUInt, false));
expect (beast::zero == mulRatio (tiny, maxUInt - 1, maxUInt, false));
// tiny negative numbers
IOUAmount tinyNeg (-minMantissa, minExponent);
// Round up should give zero
expect (zero == mulRatio (tinyNeg, 1, maxUInt, true));
expect (zero == mulRatio (tinyNeg, maxUInt - 1, maxUInt, true));
// rounding down should be tiny
expect (tinyNeg == mulRatio (tinyNeg, 1, maxUInt, false));
expect (tinyNeg == mulRatio (tinyNeg, maxUInt - 1, maxUInt, false));
}
{
// rounding
{
IOUAmount one (1, 0);
auto const rup = mulRatio (one, maxUInt - 1, maxUInt, true);
auto const rdown = mulRatio (one, maxUInt - 1, maxUInt, false);
expect (rup.mantissa () - rdown.mantissa () == 1);
}
{
IOUAmount big (maxMantissa, maxExponent);
auto const rup = mulRatio (big, maxUInt - 1, maxUInt, true);
auto const rdown = mulRatio (big, maxUInt - 1, maxUInt, false);
expect (rup.mantissa () - rdown.mantissa () == 1);
}
{
IOUAmount negOne (-1, 0);
auto const rup = mulRatio (negOne, maxUInt - 1, maxUInt, true);
auto const rdown = mulRatio (negOne, maxUInt - 1, maxUInt, false);
expect (rup.mantissa () - rdown.mantissa () == 1);
}
}
{
// division by zero
IOUAmount one (1, 0);
except ([&] {mulRatio (one, 1, 0, true);});
}
{
// overflow
IOUAmount big (maxMantissa, maxExponent);
except ([&] {mulRatio (big, 2, 0, true);});
}
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
void run () void run ()
@@ -169,6 +254,7 @@ public:
testBeastZero (); testBeastZero ();
testComparisons (); testComparisons ();
testToString (); testToString ();
testMulRatio ();
} }
}; };

View File

@@ -109,6 +109,86 @@ public:
} }
} }
void testMulRatio()
{
testcase ("mulRatio");
constexpr auto maxUInt32 = std::numeric_limits<std::uint32_t>::max ();
constexpr auto maxUInt64 = std::numeric_limits<std::uint64_t>::max ();
{
// multiply by a number that would overflow then divide by the same
// number, and check we didn't lose any value
XRPAmount big (maxUInt64);
expect (big == mulRatio (big, maxUInt32, maxUInt32, true));
// rounding mode shouldn't matter as the result is exact
expect (big == mulRatio (big, maxUInt32, maxUInt32, false));
}
{
// Similar test as above, but for neative values
XRPAmount big (maxUInt64);
expect (big == mulRatio (big, maxUInt32, maxUInt32, true));
// rounding mode shouldn't matter as the result is exact
expect (big == mulRatio (big, maxUInt32, maxUInt32, false));
}
{
// small amounts
XRPAmount tiny (1);
// Round up should give the smallest allowable number
expect (tiny == mulRatio (tiny, 1, maxUInt32, true));
// rounding down should be zero
expect (beast::zero == mulRatio (tiny, 1, maxUInt32, false));
expect (beast::zero ==
mulRatio (tiny, maxUInt32 - 1, maxUInt32, false));
// tiny negative numbers
XRPAmount tinyNeg (-1);
// Round up should give zero
expect (zero == mulRatio (tinyNeg, 1, maxUInt32, true));
expect (zero == mulRatio (tinyNeg, maxUInt32 - 1, maxUInt32, true));
// rounding down should be tiny
expect (tinyNeg == mulRatio (tinyNeg, maxUInt32 - 1, maxUInt32, false));
}
{
// rounding
{
XRPAmount one (1);
auto const rup = mulRatio (one, maxUInt32 - 1, maxUInt32, true);
auto const rdown = mulRatio (one, maxUInt32 - 1, maxUInt32, false);
expect (rup.drops () - rdown.drops () == 1);
}
{
XRPAmount big (maxUInt64);
auto const rup = mulRatio (big, maxUInt32 - 1, maxUInt32, true);
auto const rdown = mulRatio (big, maxUInt32 - 1, maxUInt32, false);
expect (rup.drops () - rdown.drops () == 1);
}
{
XRPAmount negOne (-1);
auto const rup = mulRatio (negOne, maxUInt32 - 1, maxUInt32, true);
auto const rdown = mulRatio (negOne, maxUInt32 - 1, maxUInt32, false);
expect (rup.drops () - rdown.drops () == 1);
}
}
{
// division by zero
XRPAmount one (1);
except ([&] {mulRatio (one, 1, 0, true);});
}
{
// overflow
XRPAmount big (maxUInt64);
except ([&] {mulRatio (big, 2, 0, true);});
}
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
void run () void run ()
@@ -117,6 +197,7 @@ public:
testBeastZero (); testBeastZero ();
testComparisons (); testComparisons ();
testAddSub (); testAddSub ();
testMulRatio ();
} }
}; };

View File

@@ -404,6 +404,7 @@ ripplePathFind (std::shared_ptr<RippleLineCache> const& cache,
raSrc, // --> Account sending from. raSrc, // --> Account sending from.
ps, // --> Path set. ps, // --> Path set.
app.logs(), app.logs(),
app.config(),
&rcInput); &rcInput);
JLOG(j.info) JLOG(j.info)
@@ -431,7 +432,8 @@ ripplePathFind (std::shared_ptr<RippleLineCache> const& cache,
raDst, // --> Account to deliver to. raDst, // --> Account to deliver to.
raSrc, // --> Account sending from. raSrc, // --> Account sending from.
ps, // --> Path set. ps, // --> Path set.
app.logs()); app.logs(),
app.config());
JLOG(jpr.debug) JLOG(jpr.debug)
<< "Extra path element gives " << "Extra path element gives "
<< transHuman(rc.result()); << transHuman(rc.result());

View File

@@ -29,6 +29,11 @@
#include <ripple/app/paths/PathState.cpp> #include <ripple/app/paths/PathState.cpp>
#include <ripple/app/paths/RippleCalc.cpp> #include <ripple/app/paths/RippleCalc.cpp>
#include <ripple/app/paths/RippleLineCache.cpp> #include <ripple/app/paths/RippleLineCache.cpp>
#include <ripple/app/paths/Flow.cpp>
#include <ripple/app/paths/impl/PaySteps.cpp>
#include <ripple/app/paths/impl/DirectStep.cpp>
#include <ripple/app/paths/impl/BookStep.cpp>
#include <ripple/app/paths/impl/XRPEndpointStep.cpp>
#include <ripple/app/paths/cursor/AdvanceNode.cpp> #include <ripple/app/paths/cursor/AdvanceNode.cpp>
#include <ripple/app/paths/cursor/DeliverNodeForward.cpp> #include <ripple/app/paths/cursor/DeliverNodeForward.cpp>

View File

@@ -23,6 +23,7 @@
#include <ripple/app/tests/AmendmentTable.test.cpp> #include <ripple/app/tests/AmendmentTable.test.cpp>
#include <ripple/app/tests/CrossingLimits_test.cpp> #include <ripple/app/tests/CrossingLimits_test.cpp>
#include <ripple/app/tests/DeliverMin.test.cpp> #include <ripple/app/tests/DeliverMin.test.cpp>
#include <ripple/app/tests/Flow_test.cpp>
#include <ripple/app/tests/HashRouter_test.cpp> #include <ripple/app/tests/HashRouter_test.cpp>
#include <ripple/app/tests/MultiSign.test.cpp> #include <ripple/app/tests/MultiSign.test.cpp>
#include <ripple/app/tests/OfferStream.test.cpp> #include <ripple/app/tests/OfferStream.test.cpp>