Rough cut at ripple_path_find.

This commit is contained in:
Arthur Britto
2012-11-22 14:03:32 -08:00
parent b7e18f367b
commit 5c1605ab35
6 changed files with 194 additions and 68 deletions

View File

@@ -84,7 +84,7 @@ Pathfinder::Pathfinder(RippleAddress& srcAccountID, RippleAddress& dstAccountID,
// Returns a single path, if possible. // Returns a single path, if possible.
// --> maxSearchSteps: unused // --> maxSearchSteps: unused
// --> maxPay: unused // --> maxPay: unused
bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet) bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet, bool bAllowEmpty)
{ {
if (mLedger) { if (mLedger) {
std::queue<STPath> pqueue; std::queue<STPath> pqueue;
@@ -107,16 +107,28 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet
// Determine if path is solved. // Determine if path is solved.
// Done, if dest wants XRP and last element produces XRP. // Done, if dest wants XRP and last element produces XRP.
// Done, if dest wants non-XRP and last element is dest. if (!ele.mCurrencyID // Tail output is XRP
&& !mDstAmount.getCurrency()) {
if (!ele.mCurrencyID) { // Remove implied first.
path.mPath.erase(path.mPath.begin());
// Return the path.
retPathSet.addPath(path);
cLog(lsDEBUG) << "findPaths: adding: " << path.getJson(0);
return true;
} }
if (ele.mAccountID == mDstAccountID) {
// Done, if dest wants non-XRP and last element is dest.
// YYY Allows going through self. Is this wanted?
if (ele.mAccountID == mDstAccountID // Tail is destination
&& ele.mCurrencyID == mDstAmount.getCurrency()) { // With correct output currency.
// Found a path to the destination. // Found a path to the destination.
if (2 == path.mPath.size()) { if (!bAllowEmpty && 2 == path.mPath.size()) {
// Empty path is default. Drop it. // Empty path is default. Drop it.
// XXX Don't drop empty path - we still want an estimate.
cLog(lsDEBUG) << "findPaths: dropping empty path."; cLog(lsDEBUG) << "findPaths: dropping empty path.";
continue; continue;
} }
@@ -128,14 +140,19 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet
// Return the path. // Return the path.
retPathSet.addPath(path); retPathSet.addPath(path);
cLog(lsDEBUG) << "findPaths: adding: " << path.getJson(0);
return true; return true;
} }
bool bContinued = false;
if (!ele.mCurrencyID) { if (!ele.mCurrencyID) {
// Last element is for XRP continue with qualifying books. // Last element is for XRP continue with qualifying books.
BOOST_FOREACH(OrderBook::pointer book, mOrderBook.getXRPInBooks()) BOOST_FOREACH(OrderBook::pointer book, mOrderBook.getXRPInBooks())
{ {
// XXX Don't allow looping through same order books.
//if (!path.hasSeen(line->getAccountIDPeer().getAccountID())) //if (!path.hasSeen(line->getAccountIDPeer().getAccountID()))
{ {
STPath new_path(path); STPath new_path(path);
@@ -145,10 +162,20 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet
new_path.mCurrencyID = book->getCurrencyOut(); new_path.mCurrencyID = book->getCurrencyOut();
new_path.mCurrentAccount = book->getCurrencyOut(); new_path.mCurrentAccount = book->getCurrencyOut();
cLog(lsDEBUG) <<
boost::str(boost::format("findPaths: XRP input - %s/%s")
% STAmount::createHumanCurrency(new_path.mCurrencyID)
% RippleAddress::createHumanAccountID(new_path.mCurrentAccount));
pqueue.push(new_path); pqueue.push(new_path);
bContinued = true;
} }
} }
tLog(!bContinued, lsDEBUG)
<< boost::str(boost::format("findPaths: XRP input - dead end"));
} else { } else {
// Last element is for non-XRP continue by adding ripple lines and order books. // Last element is for non-XRP continue by adding ripple lines and order books.
@@ -164,8 +191,17 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet
ele.mCurrencyID, ele.mCurrencyID,
uint160()); uint160());
cLog(lsDEBUG) <<
boost::str(boost::format("findPaths: %s/%s --> %s/%s")
% RippleAddress::createHumanAccountID(ele.mAccountID)
% STAmount::createHumanCurrency(ele.mCurrencyID)
% RippleAddress::createHumanAccountID(line->getAccountIDPeer().getAccountID())
% STAmount::createHumanCurrency(ele.mCurrencyID));
new_path.mPath.push_back(new_ele); new_path.mPath.push_back(new_ele);
pqueue.push(new_path); pqueue.push(new_path);
bContinued = true;
} }
} }
@@ -179,17 +215,31 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet
STPath new_path(path); STPath new_path(path);
STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
cLog(lsDEBUG) <<
boost::str(boost::format("findPaths: %s/%s :: %s/%s")
% STAmount::createHumanCurrency(ele.mCurrencyID)
% RippleAddress::createHumanAccountID(ele.mAccountID)
% STAmount::createHumanCurrency(book->getCurrencyOut())
% RippleAddress::createHumanAccountID(book->getIssuerOut()));
new_path.mPath.push_back(new_ele); new_path.mPath.push_back(new_ele);
new_path.mCurrentAccount=book->getIssuerOut(); new_path.mCurrentAccount=book->getIssuerOut();
new_path.mCurrencyID=book->getCurrencyOut(); new_path.mCurrencyID=book->getCurrencyOut();
pqueue.push(new_path); pqueue.push(new_path);
bContinued = true;
} }
} }
// enumerate all adjacent nodes, construct a new path and push it into the queue tLog(!bContinued, lsDEBUG)
} // While << boost::str(boost::format("findPaths: non-XRP input - dead end"));
} // if there is a ledger }
}
else
{
cLog(lsWARNING) << boost::str(boost::format("findPaths: no ledger"));
}
return false; return false;
} }

View File

@@ -38,20 +38,20 @@ class Pathfinder
OrderBookDB mOrderBook; OrderBookDB mOrderBook;
Ledger::pointer mLedger; Ledger::pointer mLedger;
std::list<PathOption::pointer> mBuildingPaths; // std::list<PathOption::pointer> mBuildingPaths;
std::list<PathOption::pointer> mCompletePaths; // std::list<PathOption::pointer> mCompletePaths;
void addOptions(PathOption::pointer tail); // void addOptions(PathOption::pointer tail);
// returns true if any building paths are now complete? // returns true if any building paths are now complete?
bool checkComplete(STPathSet& retPathSet); bool checkComplete(STPathSet& retPathSet);
void addPathOption(PathOption::pointer pathOption); // void addPathOption(PathOption::pointer pathOption);
public: public:
Pathfinder(RippleAddress& srcAccountID, RippleAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount); Pathfinder(RippleAddress& srcAccountID, RippleAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount);
// returns false if there is no path. otherwise fills out retPath // returns false if there is no path. otherwise fills out retPath
bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet); bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet, bool bAllowEmpty);
}; };
// vim:ts=4 // vim:ts=4

View File

@@ -119,7 +119,7 @@ TER PaymentTransactor::doApply()
STAmount saDstAmountAct; STAmount saDstAmountAct;
terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX
? telBAD_PATH_COUNT ? telBAD_PATH_COUNT // Too many paths for proposed ledger.
: RippleCalc::rippleCalc( : RippleCalc::rippleCalc(
mEngine->getNodes(), mEngine->getNodes(),
saMaxAmountAct, saMaxAmountAct,
@@ -131,7 +131,8 @@ TER PaymentTransactor::doApply()
spsPaths, spsPaths,
bPartialPayment, bPartialPayment,
bLimitQuality, bLimitQuality,
bNoRippleDirect); bNoRippleDirect, // Always compute for finalizing ledger.
false); // Not standalone, delete unfundeds.
} }
else else
{ {
@@ -175,3 +176,5 @@ TER PaymentTransactor::doApply()
return terResult; return terResult;
} }
// vim:ts=4

View File

@@ -9,6 +9,7 @@
#include "RippleLines.h" #include "RippleLines.h"
#include "Wallet.h" #include "Wallet.h"
#include "RippleAddress.h" #include "RippleAddress.h"
#include "RippleCalc.h"
#include "AccountState.h" #include "AccountState.h"
#include "NicknameState.h" #include "NicknameState.h"
#include "InstanceCounter.h" #include "InstanceCounter.h"
@@ -699,7 +700,7 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
Json::Value jvResult(Json::objectValue); Json::Value jvResult(Json::objectValue);
RippleAddress raSrc; RippleAddress raSrc;
RippleAddress raDst; RippleAddress raDst;
STAmount saDst; STAmount saDstAmount;
if ( if (
// Parse raSrc. // Parse raSrc.
@@ -720,9 +721,9 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
jvResult = rpcError(rpcINVALID_PARAMS); jvResult = rpcError(rpcINVALID_PARAMS);
} }
else if ( else if (
// Parse saDst. // Parse saDstAmount.
!jvRequest.isMember("destination_amount") !jvRequest.isMember("destination_amount")
|| !saDst.bSetJson(jvRequest["destination_amount"])) || !saDstAmount.bSetJson(jvRequest["destination_amount"]))
{ {
cLog(lsINFO) << "Bad destination_amount."; cLog(lsINFO) << "Bad destination_amount.";
jvResult = rpcError(rpcINVALID_PARAMS); jvResult = rpcError(rpcINVALID_PARAMS);
@@ -739,9 +740,12 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
} }
else else
{ {
Json::Value jvSrcCurrencies = jvRequest.isMember("source_currencies"); Json::Value jvSrcCurrencies = jvRequest["source_currencies"];
Json::Value jvArray(Json::arrayValue); Json::Value jvArray(Json::arrayValue);
Ledger::pointer lpCurrent = mNetOps->getCurrentLedger();
LedgerEntrySet lesSnapshot(lpCurrent);
for (unsigned int i=0; i != jvSrcCurrencies.size(); ++i) { for (unsigned int i=0; i != jvSrcCurrencies.size(); ++i) {
Json::Value jvSource = jvSrcCurrencies[i]; Json::Value jvSource = jvSrcCurrencies[i];
uint160 srcCurrencyID; uint160 srcCurrencyID;
@@ -760,16 +764,80 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
STPathSet spsPaths; STPathSet spsPaths;
// XXX Need to add support for srcIssuerID. // XXX Need to add support for srcIssuerID.
Pathfinder pf(raSrc, raDst, srcCurrencyID, saDst); Pathfinder pf(raSrc, raDst, srcCurrencyID, saDstAmount);
if (!spsPaths.isEmpty()) pf.findPaths(5, 1, spsPaths, true);
if (spsPaths.isEmpty())
{
cLog(lsDEBUG) << "ripple_path_find: No paths found.";
}
else
{ {
// XXX Also need to check liquidity. // XXX Also need to check liquidity.
jvSource.append(spsPaths.getJson(0)); STAmount saMaxAmountAct;
STAmount saDstAmountAct;
STAmount saMaxAmount(srcCurrencyID,
!!srcIssuerID
? srcIssuerID
: !!srcCurrencyID
? raSrc.getAccountID()
: ACCOUNT_XRP,
1);
saMaxAmount.negate();
TER terResult =
RippleCalc::rippleCalc(
lesSnapshot,
saMaxAmountAct,
saDstAmountAct,
saMaxAmount, // --> -1/xxx/yyy unlimited
saDstAmount, // --> Amount to deliver.
raDst.getAccountID(), // --> Account to deliver to.
raSrc.getAccountID(), // --> Account sending from.
spsPaths, // --> Path set.
false, // --> bPartialPayment - XXX might allow sometimes.
// Must achive delivery goal.
false, // --> bLimitQuality - XXX might allow sometimes.
// Average quality is wanted for normal payments.
// XXX TRUE till direct path representation resolved.
true, // --> bNoRippleDirect - XXX might allow sometimes.
// XXX No reason not to take the direct, unless set is merely direct.
true); //--> Stand alone mode, don't delete unfundeds.
cLog(lsDEBUG)
<< boost::str(boost::format("ripple_path_find: saMaxAmount=%s saDstAmount=%s saMaxAmountAct=%s saDstAmountAct=%s")
% saMaxAmount
% saDstAmount
% saMaxAmountAct
% saDstAmountAct);
if (tesSUCCESS == terResult)
{
Json::Value jvEntry(Json::objectValue);
jvEntry["source_amount"] = saMaxAmountAct.getJson(0);
jvEntry["paths"] = spsPaths.getJson(0);
jvArray.append(jvEntry);
}
else
{
std::string strToken;
std::string strHuman;
transResultInfo(terResult, strToken, strHuman);
cLog(lsDEBUG)
<< boost::str(boost::format("ripple_path_find: %s %s %s")
% strToken
% strHuman
% spsPaths.getJson(0));
}
} }
} }
jvResult["results"] = jvArray; jvResult["alternatives"] = jvArray;
} }
return jvResult; return jvResult;
@@ -872,7 +940,7 @@ Json::Value RPCHandler::handleJSONSubmit(const Json::Value& jvRequest)
Pathfinder pf(srcAddress, dstAccountID, srcCurrencyID, dstAmount); Pathfinder pf(srcAddress, dstAccountID, srcCurrencyID, dstAmount);
pf.findPaths(5, 1, spsPaths); pf.findPaths(5, 1, spsPaths, false);
if (!spsPaths.isEmpty()) if (!spsPaths.isEmpty())
{ {
@@ -1067,7 +1135,7 @@ Json::Value RPCHandler::doTxHistory(const Json::Value& params)
obj["index"]=startIndex; obj["index"]=startIndex;
std::string sql = std::string sql =
str(boost::format("SELECT * FROM Transactions ORDER BY LedgerSeq desc LIMIT %u,20") boost::str(boost::format("SELECT * FROM Transactions ORDER BY LedgerSeq desc LIMIT %u,20")
% startIndex); % startIndex);
{ {

View File

@@ -2126,7 +2126,6 @@ void RippleCalc::pathNext(PathState::ref pspCur, const int iPaths, const LedgerE
} }
} }
// XXX Stand alone calculation not implemented, does not calculate required input.
TER RippleCalc::rippleCalc( TER RippleCalc::rippleCalc(
LedgerEntrySet& lesActive, // <-> --> = Fee applied to src balance. LedgerEntrySet& lesActive, // <-> --> = Fee applied to src balance.
STAmount& saMaxAmountAct, // <-- The computed input amount. STAmount& saMaxAmountAct, // <-- The computed input amount.
@@ -2138,7 +2137,8 @@ TER RippleCalc::rippleCalc(
const STPathSet& spsPaths, const STPathSet& spsPaths,
const bool bPartialPayment, const bool bPartialPayment,
const bool bLimitQuality, const bool bLimitQuality,
const bool bNoRippleDirect const bool bNoRippleDirect,
const bool bStandAlone // True, not to delete unfundeds.
) )
{ {
RippleCalc rc(lesActive); RippleCalc rc(lesActive);
@@ -2240,8 +2240,9 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build path: %d: add: %d s
terResult = temUNCERTAIN; terResult = temUNCERTAIN;
} }
STAmount saInAct = STAmount(saMaxAmountReq.getCurrency(), saMaxAmountReq.getIssuer()); saMaxAmountAct = STAmount(saMaxAmountReq.getCurrency(), saMaxAmountReq.getIssuer());
STAmount saOutAct = STAmount(saDstAmountReq.getCurrency(), saDstAmountReq.getIssuer()); saDstAmountAct = STAmount(saDstAmountReq.getCurrency(), saDstAmountReq.getIssuer());
const LedgerEntrySet lesBase = lesActive; // Checkpoint with just fees paid. const LedgerEntrySet lesBase = lesActive; // Checkpoint with just fees paid.
const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate(saDstAmountReq, saMaxAmountReq) : 0; const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate(saDstAmountReq, saMaxAmountReq) : 0;
// When processing, don't want to complicate directory walking with deletion. // When processing, don't want to complicate directory walking with deletion.
@@ -2259,8 +2260,8 @@ int iPass = 0;
{ {
if (pspCur->uQuality) if (pspCur->uQuality)
{ {
pspCur->saInAct = saInAct; // Update to current amount processed. pspCur->saInAct = saMaxAmountAct; // Update to current amount processed.
pspCur->saOutAct = saOutAct; pspCur->saOutAct = saDstAmountAct;
rc.pathNext(pspCur, vpsPaths.size(), lesCheckpoint, lesActive); // Compute increment. rc.pathNext(pspCur, vpsPaths.size(), lesCheckpoint, lesActive); // Compute increment.
@@ -2314,8 +2315,8 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be
// Record best pass' LedgerEntrySet to build off of and potentially return. // Record best pass' LedgerEntrySet to build off of and potentially return.
lesActive.swapWith(pspBest->lesEntries); lesActive.swapWith(pspBest->lesEntries);
saInAct += pspBest->saInPass; saMaxAmountAct += pspBest->saInPass;
saOutAct += pspBest->saOutPass; saDstAmountAct += pspBest->saOutPass;
if (pspBest->bConsumed) if (pspBest->bConsumed)
{ {
@@ -2323,13 +2324,13 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be
pspBest->uQuality = 0; pspBest->uQuality = 0;
} }
if (saOutAct == saDstAmountReq) if (saDstAmountAct == saDstAmountReq)
{ {
// Done. Delivered requested amount. // Done. Delivered requested amount.
terResult = tesSUCCESS; terResult = tesSUCCESS;
} }
else if (saInAct != saMaxAmountReq && iDry != vpsPaths.size()) else if (saMaxAmountAct != saMaxAmountReq && iDry != vpsPaths.size())
{ {
// Have not met requested amount or max send, try to do more. Prepare for next pass. // Have not met requested amount or max send, try to do more. Prepare for next pass.
@@ -2359,7 +2360,7 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be
lesActive = lesBase; // Revert to just fees charged. lesActive = lesBase; // Revert to just fees charged.
} }
// Partial payment ok. // Partial payment ok.
else if (!saOutAct) else if (!saDstAmountAct)
{ {
// No payment at all. // No payment at all.
terResult = tepPATH_DRY; terResult = tepPATH_DRY;
@@ -2371,6 +2372,8 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be
} }
} }
if (!bStandAlone)
{
if (tesSUCCESS == terResult) if (tesSUCCESS == terResult)
{ {
// Delete became unfunded offers. // Delete became unfunded offers.
@@ -2387,6 +2390,7 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be
if (tesSUCCESS == terResult) if (tesSUCCESS == terResult)
terResult = lesActive.offerDelete(uOfferIndex); terResult = lesActive.offerDelete(uOfferIndex);
} }
}
return terResult; return terResult;
} }

View File

@@ -34,7 +34,7 @@ protected:
// For offers: // For offers:
STAmount saRateMax; // XXX Should rate be sticky for forward too? STAmount saRateMax;
// Directory // Directory
uint256 uDirectTip; // Current directory. uint256 uDirectTip; // Current directory.
@@ -186,7 +186,8 @@ public:
const STPathSet& spsPaths, const STPathSet& spsPaths,
const bool bPartialPayment, const bool bPartialPayment,
const bool bLimitQuality, const bool bLimitQuality,
const bool bNoRippleDirect const bool bNoRippleDirect,
const bool bStandAlone
); );
}; };