Remove old-style pathfinding code

All cases that still used the old RPF code now use new-style pathfinding.
This includes unit tests, RPF requests with a ledger specified, and RPF
requests in standalone mode.
This commit is contained in:
JoelKatz
2016-08-11 01:06:32 -07:00
committed by seelabs
parent ab45c490d7
commit fc73fbd050
9 changed files with 122 additions and 469 deletions

View File

@@ -3355,8 +3355,6 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\rpc\json_body.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\rpc\RipplePathFind.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\rpc\Role.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\rpc\RPCHandler.h">

View File

@@ -3813,9 +3813,6 @@
<ClInclude Include="..\..\src\ripple\rpc\json_body.h">
<Filter>ripple\rpc</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\rpc\RipplePathFind.h">
<Filter>ripple\rpc</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\rpc\Role.h">
<Filter>ripple\rpc</Filter>
</ClInclude>

View File

@@ -59,6 +59,9 @@ public:
public:
// VFALCO TODO Break the cyclic dependency on InfoSub
// path_find semantics
// Subscriber is updated
PathRequest (
Application& app,
std::shared_ptr <InfoSub> const& subscriber,
@@ -66,6 +69,8 @@ public:
PathRequests&,
beast::Journal journal);
// ripple_path_find semantics
// Completion function is called
PathRequest (
Application& app,
std::function <void (void)> const& completion,

View File

@@ -218,7 +218,7 @@ PathRequests::makePathRequest(
insertPathRequest (req);
app_.getLedgerMaster().newPathRequest();
}
return result.second;
return std::move (result.second);
}
// Make an old-style ripple_path_find request
@@ -249,7 +249,24 @@ PathRequests::makeLegacyPathRequest(
app_.getLedgerMaster().newPathRequest();
}
return result.second;
return std::move (result.second);
}
Json::Value
PathRequests::doLegacyPathRequest (
Resource::Consumer& consumer,
std::shared_ptr<ReadView const> const& inLedger,
Json::Value const& request)
{
auto cache = std::make_shared<RippleLineCache> (inLedger);
auto req = std::make_shared<PathRequest> (app_, []{},
consumer, ++mLastIdentifier, *this, mJournal);
auto result = req->doCreate (cache, request);
if (result.first)
result.second = req->doUpdate (cache, false);
return std::move (result.second);
}
} // ripple

View File

@@ -49,11 +49,16 @@ public:
std::shared_ptr<RippleLineCache> getLineCache (
std::shared_ptr <ReadView const> const& ledger, bool authoritative);
// Create a new-style path request that pushes
// updates to a subscriber
Json::Value makePathRequest (
std::shared_ptr <InfoSub> const& subscriber,
std::shared_ptr<ReadView const> const& ledger,
Json::Value const& request);
// Create an old-style path request that is
// managed by a coroutine and updated by
// the path engine
Json::Value makeLegacyPathRequest (
PathRequest::pointer& req,
std::function <void (void)> completion,
@@ -61,6 +66,13 @@ public:
std::shared_ptr<ReadView const> const& inLedger,
Json::Value const& request);
// Execute an old-style path request immediately
// with the ledger specified by the caller
Json::Value doLegacyPathRequest (
Resource::Consumer& consumer,
std::shared_ptr<ReadView const> const& inLedger,
Json::Value const& request);
void reportFast (std::chrono::milliseconds ms)
{
mFast.notify (ms);

View File

@@ -29,7 +29,6 @@
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/Tuning.h>
#include <ripple/rpc/RipplePathFind.h>
#include <ripple/rpc/RPCHandler.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
@@ -143,52 +142,6 @@ equal(STAmount const& sa1, STAmount const& sa2)
sa1.issue().account == sa2.issue().account;
}
std::tuple <STPathSet, STAmount, STAmount>
find_paths(jtx::Env& env,
jtx::Account const& src, jtx::Account const& dst,
STAmount const& saDstAmount,
boost::optional<STAmount> const& saSendMax = boost::none)
{
static int const level = 8;
auto const& view = env.current();
auto cache = std::make_shared<RippleLineCache>(view);
auto currencies = accountSourceCurrencies(src, cache, true);
auto jvSrcCurrencies = Json::Value(Json::arrayValue);
for (auto const& c : currencies)
{
Json::Value jvCurrency = Json::objectValue;
jvCurrency[jss::currency] = to_string(c);
jvSrcCurrencies.append(jvCurrency);
}
auto const convert_all =
saDstAmount == STAmount(saDstAmount.issue(), 1u, 0, true);
auto result = ripplePathFind(cache, src.id(), dst.id(),
(convert_all ? STAmount(saDstAmount.issue(), STAmount::cMaxValue,
STAmount::cMaxOffset) : saDstAmount), jvSrcCurrencies, boost::none,
level, saSendMax, convert_all, env.app());
if (! result.first)
{
Throw<std::runtime_error> (
"Path_test::findPath: ripplePathFind find failed");
}
auto const& jv = result.second[0u];
Json::Value paths;
paths["Paths"] = jv["paths_computed"];
STParsedJSONObject stp("generic", paths);
STAmount sa;
if (jv.isMember(jss::source_amount))
sa = amountFromJson(sfGeneric, jv[jss::source_amount]);
STAmount da;
if (jv.isMember(jss::destination_amount))
da = amountFromJson(sfGeneric, jv[jss::destination_amount]);
return std::make_tuple(
std::move(stp.object->getFieldPathSet(sfPaths)),
std::move(sa), std::move(da));
}
Json::Value
rpf(jtx::Account const& src, jtx::Account const& dst, std::uint32_t num_src)
{
@@ -252,6 +205,79 @@ public:
}
};
std::tuple <STPathSet, STAmount, STAmount>
find_paths(jtx::Env& env,
jtx::Account const& src, jtx::Account const& dst,
STAmount const& saDstAmount,
boost::optional<STAmount> const& saSendMax = boost::none)
{
using namespace jtx;
auto& app = env.app();
Resource::Charge loadType = Resource::feeReferenceRPC;
Resource::Consumer c;
RPC::Context context {beast::Journal(), {}, app, loadType,
app.getOPs(), app.getLedgerMaster(), c, Role::USER, {}};
Json::Value params = Json::objectValue;
params[jss::command] = "ripple_path_find";
params[jss::source_account] = toBase58 (src);
params[jss::destination_account] = toBase58 (dst);
params[jss::destination_amount] = saDstAmount.getJson(0);
if (saSendMax)
params[jss::send_max] = saSendMax->getJson(0);
Json::Value result;
gate g;
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
[&](auto const& coro)
{
context.params = std::move (params);
context.jobCoro = coro;
RPC::doCommand (context, result);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(! result.isMember(jss::error));
STAmount da;
if (result.isMember(jss::destination_amount))
da = amountFromJson(sfGeneric,
result[jss::destination_amount]);
STAmount sa;
STPathSet paths;
if (result.isMember(jss::alternatives))
{
auto const& alts = result[jss::alternatives];
if (alts.size() > 0)
{
auto const& path = alts[0u];
if (path.isMember(jss::source_amount))
sa = amountFromJson(sfGeneric,
path[jss::source_amount]);
if (path.isMember(jss::destination_amount))
da = amountFromJson(sfGeneric,
path[jss::destination_amount]);
if (path.isMember(jss::paths_computed))
{
Json::Value p;
p["Paths"] = path[jss::paths_computed];
STParsedJSONObject po("generic", p);
paths = po.object->getFieldPathSet (sfPaths);
}
}
}
return std::make_tuple(
std::move(paths), std::move(sa), std::move(da));
}
void
source_currencies_limit()
{

View File

@@ -98,8 +98,12 @@ Disposition Consumer::disposition() const
Disposition Consumer::charge (Charge const& what)
{
assert (m_entry != nullptr);
return m_logic->charge (*m_entry, what);
Disposition d = ok;
if (m_logic && m_entry)
d = m_logic->charge (*m_entry, what);
return d;
}
bool Consumer::warn ()

View File

@@ -1,39 +0,0 @@
//------------------------------------------------------------------------------
/*
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_RPC_RIPPLEPATHFIND_H_INCLUDED
#define RIPPLE_RPC_RIPPLEPATHFIND_H_INCLUDED
#include <ripple/app/paths/RippleLineCache.h>
#include <ripple/app/ledger/Ledger.h>
namespace ripple {
std::pair<bool, Json::Value>
ripplePathFind (std::shared_ptr<RippleLineCache> const& cache,
AccountID const& raSrc, AccountID const& raDst,
STAmount const& saDstAmount,
Json::Value const& jvSrcCurrencies,
boost::optional<Json::Value> const& contextPaths,
int const level, boost::optional<STAmount> saSendMax,
bool convert_all, Application& app);
}
#endif

View File

@@ -18,7 +18,6 @@
//==============================================================================
#include <BeastConfig.h>
#include <ripple/rpc/RipplePathFind.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/LoadFeeTrack.h>
@@ -39,7 +38,6 @@
#include <ripple/protocol/types.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/RipplePathFind.h>
#include <ripple/rpc/impl/LegacyPathFind.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/impl/Tuning.h>
@@ -65,6 +63,8 @@ Json::Value doRipplePathFind (RPC::Context& context)
! context.params.isMember(jss::ledger_index) &&
! context.params.isMember(jss::ledger_hash))
{
// No ledger specified, use pathfinding defaults
// and dispatch to pathfinding engine
if (context.app.getLedgerMaster().getValidatedLedgerAge() >
RPC::Tuning::maxValidatedLedgerAge)
{
@@ -95,380 +95,13 @@ Json::Value doRipplePathFind (RPC::Context& context)
if (! lpf.isOk ())
return rpcError (rpcTOO_BUSY);
AccountID raSrc;
AccountID raDst;
STAmount saDstAmount;
if (! context.params.isMember (jss::source_account))
{
jvResult = rpcError (rpcSRC_ACT_MISSING);
}
else if (! deprecatedParseBase58(raSrc,
context.params[jss::source_account]))
{
jvResult = rpcError (rpcSRC_ACT_MALFORMED);
}
else if (!context.params.isMember (jss::destination_account))
{
jvResult = rpcError (rpcDST_ACT_MISSING);
}
else if (! deprecatedParseBase58 (raDst,
context.params[jss::destination_account]))
{
jvResult = rpcError (rpcDST_ACT_MALFORMED);
}
else if (
// Parse saDstAmount.
!context.params.isMember (jss::destination_amount)
|| ! amountFromJsonNoThrow(saDstAmount, context.params[jss::destination_amount])
|| (saDstAmount <= zero && saDstAmount !=
STAmount(saDstAmount.issue(), 1u, 0, true))
|| (!isXRP(saDstAmount.getCurrency ())
&& (!saDstAmount.getIssuer () ||
noAccount() == saDstAmount.getIssuer ())))
{
JLOG (context.j.info()) << "Bad destination_amount.";
jvResult = rpcError (rpcINVALID_PARAMS);
}
else if (
// Checks on source_currencies.
context.params.isMember(jss::source_currencies) &&
(! context.params[jss::source_currencies].isArray() ||
context.params[jss::source_currencies].size() == 0 ||
context.params[jss::source_currencies].size() >
RPC::Tuning::max_src_cur))
{
JLOG (context.j.info()) << "Bad source_currencies.";
jvResult = rpcError(rpcINVALID_PARAMS);
}
else
{
std::shared_ptr<RippleLineCache> cache;
auto result = context.app.getPathRequests().doLegacyPathRequest (
context.consumer, lpLedger, context.params);
if (lpLedger)
{
// The caller specified a ledger
cache = std::make_shared<RippleLineCache>(lpLedger);
}
else
{
// The closed ledger is recent and any nodes made resident
// have the best chance to persist
lpLedger = context.ledgerMaster.getClosedLedger();
cache = context.app.getPathRequests().getLineCache(lpLedger, false);
}
for (auto &fieldName : jvResult.getMemberNames ())
result[fieldName] = std::move (jvResult[fieldName]);
Json::Value jvSrcCurrencies;
if (context.params.isMember (jss::source_currencies))
{
jvSrcCurrencies = context.params[jss::source_currencies];
}
else
{
auto currencies = accountSourceCurrencies(raSrc, cache, true);
if (currencies.size() > RPC::Tuning::max_auto_src_cur)
return rpcError(rpcINTERNAL);
auto jvSrcCurrencies = Json::Value(Json::arrayValue);
for (auto const& c : currencies)
{
Json::Value jvCurrency(Json::objectValue);
jvCurrency[jss::currency] = to_string(c);
jvSrcCurrencies.append(jvCurrency);
}
}
// Fill in currencies destination will accept
Json::Value jvDestCur (Json::arrayValue);
// TODO(tom): this could be optimized the same way that
// PathRequest::doUpdate() is - if we don't obsolete this code first.
auto usDestCurrID = accountDestCurrencies (raDst, cache, true);
for (auto const& uCurrency: usDestCurrID)
jvDestCur.append (to_string (uCurrency));
jvResult[jss::destination_currencies] = jvDestCur;
jvResult[jss::destination_account] = context.app.accountIDCache().toBase58(raDst);
int level = context.app.config().PATH_SEARCH_OLD;
if ((context.app.config().PATH_SEARCH_MAX > level)
&& !context.app.getFeeTrack().isLoadedLocal())
{
++level;
}
if (context.params.isMember(jss::search_depth)
&& context.params[jss::search_depth].isIntegral())
{
int rLev = context.params[jss::search_depth].asInt ();
if ((rLev < level) || (isUnlimited (context.role)))
level = rLev;
}
auto const convert_all =
saDstAmount == STAmount(saDstAmount.issue(), 1u, 0, true);
boost::optional<STAmount> saSendMax;
if (context.params.isMember(jss::send_max))
{
// Send_max requires destination amount to be -1.
if (! convert_all)
return rpcError(rpcDST_AMT_MALFORMED);
saSendMax.emplace();
if (!amountFromJsonNoThrow(
*saSendMax, context.params[jss::send_max]) ||
(saSendMax->getCurrency().isZero() &&
saSendMax->getIssuer().isNonZero()) ||
(saSendMax->getCurrency() == badCurrency()) ||
(*saSendMax <= zero &&
*saSendMax != STAmount(saSendMax->issue(), 1u, 0, true)))
{
return rpcError(rpcSENDMAX_MALFORMED);
}
}
auto contextPaths = context.params.isMember(jss::paths) ?
boost::optional<Json::Value>(context.params[jss::paths]) :
boost::optional<Json::Value>(boost::none);
auto pathFindResult = ripplePathFind(
cache, raSrc, raDst, (convert_all ? STAmount(saDstAmount.issue(),
STAmount::cMaxValue, STAmount::cMaxOffset) : saDstAmount),
jvSrcCurrencies, contextPaths, level, saSendMax,
convert_all, context.app);
if (!pathFindResult.first)
return pathFindResult.second;
// Each alternative differs by source currency.
jvResult[jss::alternatives] = pathFindResult.second;
}
JLOG (context.j.debug())
<< "ripple_path_find< " << jvResult;
return jvResult;
}
std::unique_ptr<Pathfinder> const&
getPathFinder(std::shared_ptr<RippleLineCache> const& cache, AccountID const& raSrc,
AccountID const& raDst, boost::optional<STAmount> saSendMax,
hash_map<Currency, std::unique_ptr<Pathfinder>>& currency_map,
Currency const& currency, STAmount const& dst_amount,
int const level, Application& app)
{
auto i = currency_map.find(currency);
if (i != currency_map.end())
return i->second;
auto pathfinder = std::make_unique<Pathfinder>(cache, raSrc, raDst,
currency, boost::none, dst_amount, saSendMax, app);
if (pathfinder->findPaths(level))
pathfinder->computePathRanks(max_paths);
else
pathfinder.reset(); // It's a bad request - clear it.
return currency_map[currency] = std::move(pathfinder);
}
std::pair<bool, Json::Value>
ripplePathFind (std::shared_ptr<RippleLineCache> const& cache,
AccountID const& raSrc, AccountID const& raDst,
STAmount const& saDstAmount, Json::Value const& jvSrcCurrencies,
boost::optional<Json::Value> const& contextPaths,
int const level, boost::optional<STAmount> saSendMax,
bool convert_all, Application& app)
{
Json::Value jvArray(Json::arrayValue);
hash_map<Currency, std::unique_ptr<Pathfinder>> currency_map;
auto j = app.journal ("RPCHandler");
for (auto const& c : jvSrcCurrencies)
{
if (! c.isObject())
return std::make_pair(false, rpcError(rpcINVALID_PARAMS));
// Mandatory currency
Currency srcCurrencyID;
if (! c.isMember(jss::currency) ||
! to_currency(srcCurrencyID, c[jss::currency].asString()))
{
JLOG (j.info()) << "Bad currency.";
return std::make_pair(false, rpcError(rpcSRC_CUR_MALFORMED));
}
// Optional issuer
AccountID srcIssuerID;
if (c.isMember (jss::issuer) &&
(! c[jss::issuer].isString() ||
! to_issuer(srcIssuerID, c[jss::issuer].asString()) ||
srcIssuerID.isZero() != srcCurrencyID.isZero() ||
noAccount() == srcIssuerID))
{
JLOG (j.info()) << "Bad issuer.";
return std::make_pair(false, rpcError(rpcSRC_ISR_MALFORMED));
}
if (srcIssuerID.isZero())
srcIssuerID = raSrc;
auto issue = Issue(srcCurrencyID, srcIssuerID);
if (saSendMax)
{
// If the currencies don't match, ignore the source currency.
if (srcCurrencyID != saSendMax->getCurrency())
continue;
// If neither is the source and they are not equal, then the
// source issuer is illegal.
if (srcIssuerID != raSrc && saSendMax->getIssuer() != raSrc &&
srcIssuerID != saSendMax->getIssuer())
{
return std::make_pair(false, rpcError(rpcSRC_ISR_MALFORMED));
}
// If both are the source, use the source.
// Otherwise, use the one that's not the source.
if (srcIssuerID == raSrc)
{
issue.account = saSendMax->getIssuer() != raSrc ?
saSendMax->getIssuer() : raSrc;
}
}
STPathSet spsComputed;
if (contextPaths)
{
Json::Value pathSet = Json::objectValue;
pathSet[jss::Paths] = contextPaths.get();
STParsedJSONObject paths("pathSet", pathSet);
if (! paths.object)
return std::make_pair(false, paths.error);
spsComputed = paths.object->getFieldPathSet(sfPaths);
JLOG (j.trace()) << "ripple_path_find: Paths: " <<
spsComputed.getJson(0);
}
auto& pathfinder = getPathFinder(cache, raSrc, raDst, saSendMax,
currency_map, issue.currency, saDstAmount, level, app);
if (! pathfinder)
{
JLOG (j.warn()) << "ripple_path_find: No paths found.";
continue;
}
STPath fullLiquidityPath;
auto ps = pathfinder->getBestPaths(max_paths,
fullLiquidityPath, spsComputed, issue.account);
STAmount saMaxAmount;
if (saSendMax)
{
saMaxAmount = *saSendMax;
}
else
{
AccountID issuerID;
if (isXRP(srcIssuerID))
{
// Default to source account.
if(isXRP(srcCurrencyID))
issuerID = xrpAccount();
else
issuerID = raSrc;
}
else
{
// Use specifed issuer.
issuerID = srcIssuerID;
}
saMaxAmount = STAmount(
{srcCurrencyID, issuerID}, 1u, 0, true);
}
boost::optional<PaymentSandbox> sandbox;
sandbox.emplace(&*cache->getLedger(), tapNONE);
assert(sandbox->open());
path::RippleCalc::Input rcInput;
if (convert_all)
rcInput.partialPaymentAllowed = true;
auto rc = path::RippleCalc::rippleCalculate(
*sandbox,
saMaxAmount, // --> Amount to send is unlimited
// to get an estimate.
saDstAmount, // --> Amount to deliver.
raDst, // --> Account to deliver to.
raSrc, // --> Account sending from.
ps, // --> Path set.
app.logs(),
app.config(),
&rcInput);
JLOG(j.info())
<< "ripple_path_find:"
<< " saMaxAmount=" << saMaxAmount
<< " saDstAmount=" << saDstAmount
<< " saMaxAmountAct=" << rc.actualAmountIn
<< " saDstAmountAct=" << rc.actualAmountOut;
if (! convert_all &&
! fullLiquidityPath.empty() &&
(rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL))
{
auto jpr = app.journal("PathRequest");
JLOG(jpr.debug())
<< "Trying with an extra path element";
ps.push_back(fullLiquidityPath);
sandbox.emplace(&*cache->getLedger(), tapNONE);
assert(sandbox->open());
rc = path::RippleCalc::rippleCalculate(
*sandbox,
saMaxAmount, // --> Amount to send is unlimited
// to get an estimate.
saDstAmount, // --> Amount to deliver.
raDst, // --> Account to deliver to.
raSrc, // --> Account sending from.
ps, // --> Path set.
app.logs(),
app.config());
JLOG(jpr.debug())
<< "Extra path element gives "
<< transHuman(rc.result());
}
if (rc.result() == tesSUCCESS)
{
Json::Value jvEntry(Json::objectValue);
STPathSet spsCanonical;
// Reuse the expanded as it would need to be calcuated
// anyway to produce the canonical. (At least unless we
// make a direct canonical.)
jvEntry[jss::source_amount] = rc.actualAmountIn.getJson(0);
jvEntry[jss::paths_canonical] = Json::arrayValue;
jvEntry[jss::paths_computed] = ps.getJson(0);
if (convert_all)
jvEntry[jss::destination_amount] = rc.actualAmountOut.getJson(0);
jvArray.append(jvEntry);
}
else
{
std::string strToken;
std::string strHuman;
transResultInfo(rc.result(), strToken, strHuman);
JLOG (j.debug())
<< "ripple_path_find: "
<< strToken << " "
<< strHuman << " "
<< spsComputed.getJson(0);
}
}
return std::make_pair(true, std::move(jvArray));
return result;
}
} // ripple