mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 22:45:52 +00:00
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:
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
154
src/ripple/app/paths/Flow.cpp
Normal file
154
src/ripple/app/paths/Flow.cpp
Normal 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
|
||||||
60
src/ripple/app/paths/Flow.h
Normal file
60
src/ripple/app/paths/Flow.h
Normal 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
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
613
src/ripple/app/paths/impl/BookStep.cpp
Normal file
613
src/ripple/app/paths/impl/BookStep.cpp
Normal 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
|
||||||
636
src/ripple/app/paths/impl/DirectStep.cpp
Normal file
636
src/ripple/app/paths/impl/DirectStep.cpp
Normal 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
|
||||||
502
src/ripple/app/paths/impl/PaySteps.cpp
Normal file
502
src/ripple/app/paths/impl/PaySteps.cpp
Normal 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
|
||||||
95
src/ripple/app/paths/impl/StepChecks.h
Normal file
95
src/ripple/app/paths/impl/StepChecks.h
Normal 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
|
||||||
384
src/ripple/app/paths/impl/Steps.h
Normal file
384
src/ripple/app/paths/impl/Steps.h
Normal 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
|
||||||
509
src/ripple/app/paths/impl/StrandFlow.h
Normal file
509
src/ripple/app/paths/impl/StrandFlow.h
Normal 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
|
||||||
289
src/ripple/app/paths/impl/XRPEndpointStep.cpp
Normal file
289
src/ripple/app/paths/impl/XRPEndpointStep.cpp
Normal 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
|
||||||
692
src/ripple/app/tests/Flow_test.cpp
Normal file
692
src/ripple/app/tests/Flow_test.cpp
Normal 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
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ();
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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*
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
110
src/ripple/protocol/AmountConversions.h
Normal file
110
src/ripple/protocol/AmountConversions.h
Normal 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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -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&
|
||||||
|
|||||||
@@ -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> ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 ();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 ();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user