mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Move ./modules to ./src
This commit is contained in:
411
src/ripple_app/paths/ripple_PathRequest.cpp
Normal file
411
src/ripple_app/paths/ripple_PathRequest.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (PathRequest)
|
||||
|
||||
// VFALCO TODO Move these globals into a PathRequests collection inteface
|
||||
PathRequest::StaticLockType PathRequest::sLock ("PathRequest", __FILE__, __LINE__);
|
||||
std::set <PathRequest::wptr> PathRequest::sRequests;
|
||||
|
||||
PathRequest::PathRequest (const boost::shared_ptr<InfoSub>& subscriber)
|
||||
: mLock (this, "PathRequest", __FILE__, __LINE__)
|
||||
, wpSubscriber (subscriber)
|
||||
, jvStatus (Json::objectValue)
|
||||
, bValid (false)
|
||||
, bNew (true)
|
||||
, iLastLevel (0)
|
||||
, bLastSuccess (false)
|
||||
{
|
||||
}
|
||||
|
||||
bool PathRequest::isValid ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return bValid;
|
||||
}
|
||||
|
||||
bool PathRequest::isNew ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return bNew;
|
||||
}
|
||||
|
||||
bool PathRequest::isValid (Ledger::ref lrLedger)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
bValid = raSrcAccount.isSet () && raDstAccount.isSet () && saDstAmount.isPositive ();
|
||||
|
||||
if (bValid)
|
||||
{
|
||||
AccountState::pointer asSrc = getApp().getOPs ().getAccountState (lrLedger, raSrcAccount);
|
||||
|
||||
if (!asSrc)
|
||||
{
|
||||
// no source account
|
||||
bValid = false;
|
||||
jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND);
|
||||
}
|
||||
else
|
||||
{
|
||||
AccountState::pointer asDst = getApp().getOPs ().getAccountState (lrLedger, raDstAccount);
|
||||
Json::Value jvDestCur;
|
||||
|
||||
if (!asDst)
|
||||
{
|
||||
// no destination account
|
||||
jvDestCur.append (Json::Value ("XRP"));
|
||||
|
||||
if (!saDstAmount.isNative ())
|
||||
{
|
||||
// only XRP can be send to a non-existent account
|
||||
bValid = false;
|
||||
jvStatus = rpcError (rpcACT_NOT_FOUND);
|
||||
}
|
||||
else if (saDstAmount < STAmount (lrLedger->getReserve (0)))
|
||||
{
|
||||
// payment must meet reserve
|
||||
bValid = false;
|
||||
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::unordered_set<uint160> usDestCurrID = usAccountDestCurrencies (raDstAccount, lrLedger, true);
|
||||
BOOST_FOREACH (const uint160 & uCurrency, usDestCurrID)
|
||||
jvDestCur.append (STAmount::createHumanCurrency (uCurrency));
|
||||
jvStatus["destination_tag"] = (asDst->peekSLE ().getFlags () & lsfRequireDestTag) != 0;
|
||||
}
|
||||
|
||||
jvStatus["destination_currencies"] = jvDestCur;
|
||||
}
|
||||
}
|
||||
|
||||
jvStatus["ledger_hash"] = lrLedger->getHash ().GetHex ();
|
||||
jvStatus["ledger_index"] = lrLedger->getLedgerSeq ();
|
||||
return bValid;
|
||||
}
|
||||
|
||||
Json::Value PathRequest::doCreate (Ledger::ref lrLedger, const Json::Value& value)
|
||||
{
|
||||
assert (lrLedger->isClosed ());
|
||||
|
||||
Json::Value status;
|
||||
bool mValid;
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (parseJson (value, true) != PFR_PJ_INVALID)
|
||||
{
|
||||
mValid = isValid (lrLedger);
|
||||
|
||||
if (mValid)
|
||||
{
|
||||
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (lrLedger);
|
||||
doUpdate (cache, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
mValid = false;
|
||||
}
|
||||
|
||||
if (mValid)
|
||||
{
|
||||
WriteLog (lsINFO, PathRequest) << "Request created: " << raSrcAccount.humanAccountID () <<
|
||||
" -> " << raDstAccount.humanAccountID ();
|
||||
WriteLog (lsINFO, PathRequest) << "Deliver: " << saDstAmount.getFullText ();
|
||||
|
||||
StaticScopedLockType sl (sLock, __FILE__, __LINE__);
|
||||
sRequests.insert (shared_from_this ());
|
||||
}
|
||||
|
||||
return jvStatus;
|
||||
}
|
||||
|
||||
int PathRequest::parseJson (const Json::Value& jvParams, bool complete)
|
||||
{
|
||||
int ret = PFR_PJ_NOCHANGE;
|
||||
|
||||
if (jvParams.isMember ("source_account"))
|
||||
{
|
||||
if (!raSrcAccount.setAccountID (jvParams["source_account"].asString ()))
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_ACT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
}
|
||||
else if (complete)
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_ACT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (jvParams.isMember ("destination_account"))
|
||||
{
|
||||
if (!raDstAccount.setAccountID (jvParams["destination_account"].asString ()))
|
||||
{
|
||||
jvStatus = rpcError (rpcDST_ACT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
}
|
||||
else if (complete)
|
||||
{
|
||||
jvStatus = rpcError (rpcDST_ACT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (jvParams.isMember ("destination_amount"))
|
||||
{
|
||||
if (!saDstAmount.bSetJson (jvParams["destination_amount"]) ||
|
||||
(saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) ||
|
||||
(saDstAmount.getCurrency () == CURRENCY_BAD) ||
|
||||
!saDstAmount.isPositive ())
|
||||
{
|
||||
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
}
|
||||
else if (complete)
|
||||
{
|
||||
jvStatus = rpcError (rpcDST_ACT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (jvParams.isMember ("source_currencies"))
|
||||
{
|
||||
const Json::Value& jvSrcCur = jvParams["source_currencies"];
|
||||
|
||||
if (!jvSrcCur.isArray ())
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
sciSourceCurrencies.clear ();
|
||||
|
||||
for (unsigned i = 0; i < jvSrcCur.size (); ++i)
|
||||
{
|
||||
const Json::Value& jvCur = jvSrcCur[i];
|
||||
uint160 uCur, uIss;
|
||||
|
||||
if (!jvCur.isObject() || !jvCur.isMember ("currency") || !STAmount::currencyFromString (uCur, jvCur["currency"].asString ()))
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (jvCur.isMember ("issuer") && !STAmount::issuerFromString (uIss, jvCur["issuer"].asString ()))
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_ISR_MALFORMED);
|
||||
}
|
||||
|
||||
if (uCur.isZero () && uIss.isNonZero ())
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
sciSourceCurrencies.insert (currIssuer_t (uCur, uIss));
|
||||
}
|
||||
}
|
||||
|
||||
if (jvParams.isMember ("id"))
|
||||
jvId = jvParams["id"];
|
||||
|
||||
return ret;
|
||||
}
|
||||
Json::Value PathRequest::doClose (const Json::Value&)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return jvStatus;
|
||||
}
|
||||
|
||||
Json::Value PathRequest::doStatus (const Json::Value&)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return jvStatus;
|
||||
}
|
||||
|
||||
bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
jvStatus = Json::objectValue;
|
||||
|
||||
if (!isValid (cache->getLedger ()))
|
||||
return false;
|
||||
|
||||
if (!fast)
|
||||
bNew = false;
|
||||
|
||||
std::set<currIssuer_t> sourceCurrencies (sciSourceCurrencies);
|
||||
|
||||
if (sourceCurrencies.empty ())
|
||||
{
|
||||
boost::unordered_set<uint160> usCurrencies =
|
||||
usAccountSourceCurrencies (raSrcAccount, cache->getLedger (), true);
|
||||
bool sameAccount = raSrcAccount == raDstAccount;
|
||||
BOOST_FOREACH (const uint160 & c, usCurrencies)
|
||||
{
|
||||
if (!sameAccount || (c != saDstAmount.getCurrency ()))
|
||||
{
|
||||
if (c.isZero ())
|
||||
sourceCurrencies.insert (std::make_pair (c, ACCOUNT_XRP));
|
||||
else
|
||||
sourceCurrencies.insert (std::make_pair (c, raSrcAccount.getAccountID ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jvStatus["source_account"] = raSrcAccount.humanAccountID ();
|
||||
jvStatus["destination_account"] = raDstAccount.humanAccountID ();
|
||||
jvStatus["destination_amount"] = saDstAmount.getJson (0);
|
||||
|
||||
if (!jvId.isNull ())
|
||||
jvStatus["id"] = jvId;
|
||||
|
||||
Json::Value jvArray = Json::arrayValue;
|
||||
|
||||
int iLevel = iLastLevel;
|
||||
bool loaded = getApp().getFeeTrack().isLoadedLocal();
|
||||
|
||||
if (iLevel == 0)
|
||||
{ // first pass
|
||||
if (loaded)
|
||||
iLevel = getConfig().PATH_SEARCH_FAST;
|
||||
else if (!fast)
|
||||
iLevel = getConfig().PATH_SEARCH_OLD;
|
||||
else if (getConfig().PATH_SEARCH < getConfig().PATH_SEARCH_MAX)
|
||||
iLevel = getConfig().PATH_SEARCH + 1; // start with an extra boost
|
||||
else
|
||||
iLevel = getConfig().PATH_SEARCH;
|
||||
}
|
||||
else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast)
|
||||
{ // leaving fast pathfinding
|
||||
iLevel = getConfig().PATH_SEARCH;
|
||||
if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))
|
||||
--iLevel;
|
||||
else if (!loaded && (iLevel < getConfig().PATH_SEARCH))
|
||||
++iLevel;
|
||||
}
|
||||
else if (bLastSuccess)
|
||||
{ // decrement, if possible
|
||||
if ((iLevel > getConfig().PATH_SEARCH) || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)))
|
||||
--iLevel;
|
||||
}
|
||||
else
|
||||
{ // adjust as needed
|
||||
if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX))
|
||||
++iLevel;
|
||||
if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))
|
||||
--iLevel;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
BOOST_FOREACH (const currIssuer_t & currIssuer, sourceCurrencies)
|
||||
{
|
||||
{
|
||||
STAmount test (currIssuer.first, currIssuer.second, 1);
|
||||
WriteLog (lsDEBUG, PathRequest) << "Trying to find paths: " << test.getFullText ();
|
||||
}
|
||||
bool valid;
|
||||
STPathSet& spsPaths = mContext[currIssuer];
|
||||
Pathfinder pf (cache, raSrcAccount, raDstAccount,
|
||||
currIssuer.first, currIssuer.second, saDstAmount, valid);
|
||||
CondLog (!valid, lsINFO, PathRequest) << "PF request not valid";
|
||||
|
||||
if (valid && pf.findPaths (iLevel, 4, spsPaths))
|
||||
{
|
||||
LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE);
|
||||
std::vector<PathState::pointer> vpsExpanded;
|
||||
STAmount saMaxAmountAct;
|
||||
STAmount saDstAmountAct;
|
||||
STAmount saMaxAmount (currIssuer.first,
|
||||
currIssuer.second.isNonZero () ? currIssuer.second :
|
||||
(currIssuer.first.isZero () ? ACCOUNT_XRP : raSrcAccount.getAccountID ()), 1);
|
||||
saMaxAmount.negate ();
|
||||
WriteLog (lsDEBUG, PathRequest) << "Paths found, calling rippleCalc";
|
||||
TER terResult = RippleCalc::rippleCalc (lesSandbox, saMaxAmountAct, saDstAmountAct,
|
||||
vpsExpanded, saMaxAmount, saDstAmount, raDstAccount.getAccountID (), raSrcAccount.getAccountID (),
|
||||
spsPaths, false, false, false, true);
|
||||
|
||||
if (terResult == tesSUCCESS)
|
||||
{
|
||||
Json::Value jvEntry (Json::objectValue);
|
||||
jvEntry["source_amount"] = saMaxAmountAct.getJson (0);
|
||||
jvEntry["paths_computed"] = spsPaths.getJson (0);
|
||||
found = true;
|
||||
jvArray.append (jvEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsINFO, PathRequest) << "rippleCalc returns " << transHuman (terResult);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsINFO, PathRequest) << "No paths found";
|
||||
}
|
||||
}
|
||||
|
||||
iLastLevel = iLevel;
|
||||
bLastSuccess = found;
|
||||
|
||||
jvStatus["alternatives"] = jvArray;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PathRequest::updateAll (Ledger::ref ledger, bool newOnly)
|
||||
{
|
||||
std::set<wptr> requests;
|
||||
|
||||
{
|
||||
StaticScopedLockType sl (sLock, __FILE__, __LINE__);
|
||||
requests = sRequests;
|
||||
}
|
||||
|
||||
if (requests.empty ())
|
||||
return;
|
||||
|
||||
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (ledger);
|
||||
|
||||
BOOST_FOREACH (wref wRequest, requests)
|
||||
{
|
||||
bool remove = true;
|
||||
PathRequest::pointer pRequest = wRequest.lock ();
|
||||
|
||||
if (pRequest)
|
||||
{
|
||||
if (newOnly && !pRequest->isNew ())
|
||||
remove = false;
|
||||
else
|
||||
{
|
||||
InfoSub::pointer ipSub = pRequest->wpSubscriber.lock ();
|
||||
|
||||
if (ipSub)
|
||||
{
|
||||
Json::Value update;
|
||||
{
|
||||
ScopedLockType sl (pRequest->mLock, __FILE__, __LINE__);
|
||||
pRequest->doUpdate (cache, false);
|
||||
update = pRequest->jvStatus;
|
||||
}
|
||||
update["type"] = "path_find";
|
||||
ipSub->send (update, false);
|
||||
remove = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remove)
|
||||
{
|
||||
StaticScopedLockType sl (sLock, __FILE__, __LINE__);
|
||||
sRequests.erase (wRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
82
src/ripple_app/paths/ripple_PathRequest.h
Normal file
82
src/ripple_app/paths/ripple_PathRequest.h
Normal file
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PATHREQUEST_H
|
||||
#define RIPPLE_PATHREQUEST_H
|
||||
|
||||
// A pathfinding request submitted by a client
|
||||
// The request issuer must maintain a strong pointer
|
||||
|
||||
class RippleLineCache;
|
||||
|
||||
// Return values from parseJson <0 = invalid, >0 = valid
|
||||
#define PFR_PJ_INVALID -1
|
||||
#define PFR_PJ_NOCHANGE 0
|
||||
#define PFR_PJ_CHANGE 1
|
||||
|
||||
class PathRequest : public boost::enable_shared_from_this<PathRequest>
|
||||
{
|
||||
public:
|
||||
typedef boost::weak_ptr<PathRequest> wptr;
|
||||
typedef boost::shared_ptr<PathRequest> pointer;
|
||||
typedef const pointer& ref;
|
||||
typedef const wptr& wref;
|
||||
typedef std::pair<uint160, uint160> currIssuer_t;
|
||||
|
||||
public:
|
||||
// VFALCO TODO Break the cyclic dependency on InfoSub
|
||||
explicit PathRequest (boost::shared_ptr <InfoSub> const& subscriber);
|
||||
|
||||
bool isValid (const boost::shared_ptr<Ledger>&);
|
||||
bool isValid ();
|
||||
bool isNew ();
|
||||
Json::Value getStatus ();
|
||||
|
||||
Json::Value doCreate (const boost::shared_ptr<Ledger>&, const Json::Value&);
|
||||
Json::Value doClose (const Json::Value&);
|
||||
Json::Value doStatus (const Json::Value&);
|
||||
|
||||
bool doUpdate (const boost::shared_ptr<RippleLineCache>&, bool fast); // update jvStatus
|
||||
|
||||
static void updateAll (const boost::shared_ptr<Ledger>& ledger, bool newOnly);
|
||||
|
||||
private:
|
||||
void setValid ();
|
||||
int parseJson (const Json::Value&, bool complete);
|
||||
|
||||
typedef RippleRecursiveMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
boost::weak_ptr<InfoSub> wpSubscriber; // Who this request came from
|
||||
Json::Value jvId;
|
||||
Json::Value jvStatus; // Last result
|
||||
|
||||
// Client request parameters
|
||||
RippleAddress raSrcAccount;
|
||||
RippleAddress raDstAccount;
|
||||
STAmount saDstAmount;
|
||||
std::set<currIssuer_t> sciSourceCurrencies;
|
||||
std::vector<Json::Value> vjvBridges;
|
||||
std::map<currIssuer_t, STPathSet> mContext;
|
||||
|
||||
bool bValid;
|
||||
bool bNew;
|
||||
|
||||
int iLastLevel;
|
||||
bool bLastSuccess;
|
||||
|
||||
// Track all requests
|
||||
static std::set<wptr> sRequests;
|
||||
|
||||
typedef RippleRecursiveMutex StaticLockType;
|
||||
typedef LockType::ScopedLockType StaticScopedLockType;
|
||||
static StaticLockType sLock;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
794
src/ripple_app/paths/ripple_PathState.cpp
Normal file
794
src/ripple_app/paths/ripple_PathState.cpp
Normal file
@@ -0,0 +1,794 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
// TODO:
|
||||
// - Do automatic bridging via XRP.
|
||||
//
|
||||
// OPTIMIZE: When calculating path increment, note if increment consumes all liquidity. No need to revisit path in the future if
|
||||
// all liquidity is used.
|
||||
//
|
||||
|
||||
class RippleCalc; // for logging
|
||||
|
||||
std::size_t hash_value (const aciSource& asValue)
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
|
||||
asValue.get<0> ().hash_combine (seed);
|
||||
asValue.get<1> ().hash_combine (seed);
|
||||
asValue.get<2> ().hash_combine (seed);
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Compare the non-calculated fields.
|
||||
bool PathState::Node::operator== (const Node& pnOther) const
|
||||
{
|
||||
return pnOther.uFlags == uFlags
|
||||
&& pnOther.uAccountID == uAccountID
|
||||
&& pnOther.uCurrencyID == uCurrencyID
|
||||
&& pnOther.uIssuerID == uIssuerID;
|
||||
}
|
||||
|
||||
// This is for debugging not end users. Output names can be changed without warning.
|
||||
Json::Value PathState::Node::getJson () const
|
||||
{
|
||||
Json::Value jvNode (Json::objectValue);
|
||||
Json::Value jvFlags (Json::arrayValue);
|
||||
|
||||
jvNode["type"] = uFlags;
|
||||
|
||||
if (isSetBit (uFlags, STPathElement::typeAccount) || !!uAccountID)
|
||||
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account");
|
||||
|
||||
if (isSetBit (uFlags, STPathElement::typeCurrency) || !!uCurrencyID)
|
||||
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency");
|
||||
|
||||
if (isSetBit (uFlags, STPathElement::typeIssuer) || !!uIssuerID)
|
||||
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeIssuer) == !!uIssuerID ? "issuer" : "-issuer");
|
||||
|
||||
jvNode["flags"] = jvFlags;
|
||||
|
||||
if (!!uAccountID)
|
||||
jvNode["account"] = RippleAddress::createHumanAccountID (uAccountID);
|
||||
|
||||
if (!!uCurrencyID)
|
||||
jvNode["currency"] = STAmount::createHumanCurrency (uCurrencyID);
|
||||
|
||||
if (!!uIssuerID)
|
||||
jvNode["issuer"] = RippleAddress::createHumanAccountID (uIssuerID);
|
||||
|
||||
if (saRevRedeem)
|
||||
jvNode["rev_redeem"] = saRevRedeem.getFullText ();
|
||||
|
||||
if (saRevIssue)
|
||||
jvNode["rev_issue"] = saRevIssue.getFullText ();
|
||||
|
||||
if (saRevDeliver)
|
||||
jvNode["rev_deliver"] = saRevDeliver.getFullText ();
|
||||
|
||||
if (saFwdRedeem)
|
||||
jvNode["fwd_redeem"] = saFwdRedeem.getFullText ();
|
||||
|
||||
if (saFwdIssue)
|
||||
jvNode["fwd_issue"] = saFwdIssue.getFullText ();
|
||||
|
||||
if (saFwdDeliver)
|
||||
jvNode["fwd_deliver"] = saFwdDeliver.getFullText ();
|
||||
|
||||
return jvNode;
|
||||
}
|
||||
|
||||
//
|
||||
// PathState implementation
|
||||
//
|
||||
|
||||
// Return true, iff lhs has less priority than rhs.
|
||||
bool PathState::lessPriority (PathState& lhs, PathState& rhs)
|
||||
{
|
||||
// First rank is quality.
|
||||
if (lhs.uQuality != rhs.uQuality)
|
||||
return lhs.uQuality > rhs.uQuality; // Bigger is worse.
|
||||
|
||||
// Second rank is best quantity.
|
||||
if (lhs.saOutPass != rhs.saOutPass)
|
||||
return lhs.saOutPass < rhs.saOutPass; // Smaller is worse.
|
||||
|
||||
// Third rank is path index.
|
||||
return lhs.mIndex > rhs.mIndex; // Bigger is worse.
|
||||
}
|
||||
|
||||
// Make sure last path node delivers to uAccountID: uCurrencyID from uIssuerID.
|
||||
//
|
||||
// If the unadded next node as specified by arguments would not work as is, then add the necessary nodes so it would work.
|
||||
//
|
||||
// Rules:
|
||||
// - Currencies must be converted via an offer.
|
||||
// - A node names it's output.
|
||||
// - A ripple nodes output issuer must be the node's account or the next node's account.
|
||||
// - Offers can only go directly to another offer if the currency and issuer are an exact match.
|
||||
// - Real issuers must be specified for non-XRP.
|
||||
TER PathState::pushImply (
|
||||
const uint160& uAccountID, // --> Delivering to this account.
|
||||
const uint160& uCurrencyID, // --> Delivering this currency.
|
||||
const uint160& uIssuerID) // --> Delivering this issuer.
|
||||
{
|
||||
const Node& pnPrv = vpnNodes.back ();
|
||||
TER terResult = tesSUCCESS;
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushImply> "
|
||||
<< RippleAddress::createHumanAccountID (uAccountID)
|
||||
<< " " << STAmount::createHumanCurrency (uCurrencyID)
|
||||
<< " " << RippleAddress::createHumanAccountID (uIssuerID);
|
||||
|
||||
if (pnPrv.uCurrencyID != uCurrencyID)
|
||||
{
|
||||
// Currency is different, need to convert via an offer.
|
||||
|
||||
terResult = pushNode ( // Offer.
|
||||
!!uCurrencyID
|
||||
? STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
: STPathElement::typeCurrency,
|
||||
ACCOUNT_XRP, // Placeholder for offers.
|
||||
uCurrencyID, // The offer's output is what is now wanted.
|
||||
uIssuerID);
|
||||
}
|
||||
|
||||
const Node& pnBck = vpnNodes.back ();
|
||||
|
||||
// For ripple, non-XRP, ensure the issuer is on at least one side of the transaction.
|
||||
if (tesSUCCESS == terResult
|
||||
&& !!uCurrencyID // Not XRP.
|
||||
&& (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs.
|
||||
&& uAccountID != uIssuerID)) // Current is not receiving own IOUs.
|
||||
{
|
||||
// Need to ripple through uIssuerID's account.
|
||||
|
||||
terResult = pushNode (
|
||||
STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer,
|
||||
uIssuerID, // Intermediate account is the needed issuer.
|
||||
uCurrencyID,
|
||||
uIssuerID);
|
||||
}
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushImply< : %s") % transToken (terResult));
|
||||
|
||||
return terResult;
|
||||
}
|
||||
|
||||
// Append a node and insert before it any implied nodes.
|
||||
// Offers may go back to back.
|
||||
// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
|
||||
TER PathState::pushNode (
|
||||
const int iType,
|
||||
const uint160& uAccountID,
|
||||
const uint160& uCurrencyID,
|
||||
const uint160& uIssuerID)
|
||||
{
|
||||
Node pnCur;
|
||||
const bool bFirst = vpnNodes.empty ();
|
||||
const Node& pnPrv = bFirst ? Node () : vpnNodes.back ();
|
||||
// true, iff node is a ripple account. false, iff node is an offer node.
|
||||
const bool bAccount = isSetBit (iType, STPathElement::typeAccount);
|
||||
// true, iff currency supplied.
|
||||
// Currency is specified for the output of the current node.
|
||||
const bool bCurrency = isSetBit (iType, STPathElement::typeCurrency);
|
||||
// Issuer is specified for the output of the current node.
|
||||
const bool bIssuer = isSetBit (iType, STPathElement::typeIssuer);
|
||||
TER terResult = tesSUCCESS;
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode> "
|
||||
<< iType
|
||||
<< ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-")
|
||||
<< " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-")
|
||||
<< "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-");
|
||||
|
||||
pnCur.uFlags = iType;
|
||||
pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID;
|
||||
|
||||
if (iType & ~STPathElement::typeValidBits)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits.";
|
||||
|
||||
terResult = temBAD_PATH;
|
||||
}
|
||||
else if (bIssuer && !pnCur.uCurrencyID)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP.";
|
||||
|
||||
terResult = temBAD_PATH;
|
||||
}
|
||||
else if (bIssuer && !uIssuerID)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer.";
|
||||
|
||||
terResult = temBAD_PATH;
|
||||
}
|
||||
else if (!bAccount && !bCurrency && !bIssuer)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer.";
|
||||
|
||||
terResult = temBAD_PATH;
|
||||
}
|
||||
else if (bAccount)
|
||||
{
|
||||
// Account link
|
||||
|
||||
pnCur.uAccountID = uAccountID;
|
||||
pnCur.uIssuerID = bIssuer
|
||||
? uIssuerID
|
||||
: !!pnCur.uCurrencyID
|
||||
? uAccountID
|
||||
: ACCOUNT_XRP;
|
||||
pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID);
|
||||
pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID);
|
||||
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
|
||||
pnCur.saFwdDeliver = pnCur.saRevDeliver;
|
||||
|
||||
if (bFirst)
|
||||
{
|
||||
// The first node is always correct as is.
|
||||
|
||||
nothing ();
|
||||
}
|
||||
else if (!uAccountID)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account.";
|
||||
|
||||
terResult = temBAD_PATH;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add required intermediate nodes to deliver to current account.
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account.";
|
||||
|
||||
terResult = pushImply (
|
||||
pnCur.uAccountID, // Current account.
|
||||
pnCur.uCurrencyID, // Wanted currency.
|
||||
!!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer.
|
||||
|
||||
// Note: pnPrv may no longer be the immediately previous node.
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult && !vpnNodes.empty ())
|
||||
{
|
||||
const Node& pnBck = vpnNodes.back ();
|
||||
bool bBckAccount = isSetBit (pnBck.uFlags, STPathElement::typeAccount);
|
||||
|
||||
if (bBckAccount)
|
||||
{
|
||||
SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID));
|
||||
|
||||
if (!sleRippleState)
|
||||
{
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between "
|
||||
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
|
||||
<< " and "
|
||||
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
|
||||
<< " for "
|
||||
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
|
||||
<< "." ;
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << getJson ();
|
||||
|
||||
terResult = terNO_LINE;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between "
|
||||
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
|
||||
<< " and "
|
||||
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
|
||||
<< " for "
|
||||
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
|
||||
<< "." ;
|
||||
|
||||
SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID));
|
||||
bool bHigh = pnBck.uAccountID > pnCur.uAccountID;
|
||||
|
||||
if (!sleBck)
|
||||
{
|
||||
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID);
|
||||
|
||||
terResult = terNO_ACCOUNT;
|
||||
}
|
||||
else if ((isSetBit (sleBck->getFieldU32 (sfFlags), lsfRequireAuth)
|
||||
&& !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))
|
||||
&& sleRippleState->getFieldAmount(sfBalance).isZero()) // CHECKME
|
||||
{
|
||||
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth.";
|
||||
|
||||
terResult = terNO_AUTH;
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID);
|
||||
STAmount saLimit;
|
||||
|
||||
if (!saOwed.isPositive ()
|
||||
&& -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID)))
|
||||
{
|
||||
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("pushNode: dry: saOwed=%s saLimit=%s")
|
||||
% saOwed
|
||||
% saLimit);
|
||||
|
||||
terResult = tecPATH_DRY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
vpnNodes.push_back (pnCur);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Offer link
|
||||
// Offers bridge a change in currency & issuer or just a change in issuer.
|
||||
pnCur.uIssuerID = bIssuer
|
||||
? uIssuerID
|
||||
: !!pnCur.uCurrencyID
|
||||
? !!pnPrv.uIssuerID
|
||||
? pnPrv.uIssuerID // Default to previous issuer
|
||||
: pnPrv.uAccountID // Or previous account if no previous issuer.
|
||||
: ACCOUNT_XRP;
|
||||
pnCur.saRateMax = saZero;
|
||||
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
|
||||
pnCur.saFwdDeliver = pnCur.saRevDeliver;
|
||||
|
||||
if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer.";
|
||||
|
||||
terResult = temBAD_PATH;
|
||||
}
|
||||
else if (!!pnPrv.uAccountID)
|
||||
{
|
||||
// Previous is an account.
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer.";
|
||||
|
||||
// Insert intermediary issuer account if needed.
|
||||
terResult = pushImply (
|
||||
ACCOUNT_XRP, // Rippling, but offers don't have an account.
|
||||
pnPrv.uCurrencyID,
|
||||
pnPrv.uIssuerID);
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
vpnNodes.push_back (pnCur);
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushNode< : %s") % transToken (terResult));
|
||||
|
||||
return terResult;
|
||||
}
|
||||
|
||||
// Set to an expanded path.
|
||||
//
|
||||
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, or temBAD_PATH_LOOP
|
||||
void PathState::setExpanded (
|
||||
const LedgerEntrySet& lesSource,
|
||||
const STPath& spSourcePath,
|
||||
const uint160& uReceiverID,
|
||||
const uint160& uSenderID
|
||||
)
|
||||
{
|
||||
uQuality = 1; // Mark path as active.
|
||||
|
||||
const uint160 uMaxCurrencyID = saInReq.getCurrency ();
|
||||
const uint160 uMaxIssuerID = saInReq.getIssuer ();
|
||||
|
||||
const uint160 uOutCurrencyID = saOutReq.getCurrency ();
|
||||
const uint160 uOutIssuerID = saOutReq.getIssuer ();
|
||||
const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_XRP; // Sender is always issuer for non-XRP.
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("setExpanded> %s") % spSourcePath.getJson (0));
|
||||
|
||||
lesEntries = lesSource.duplicate ();
|
||||
|
||||
terStatus = tesSUCCESS;
|
||||
|
||||
// XRP with issuer is malformed.
|
||||
if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID))
|
||||
terStatus = temBAD_PATH;
|
||||
|
||||
// Push sending node.
|
||||
// For non-XRP, issuer is always sending account.
|
||||
// - Trying to expand, not-compact.
|
||||
// - Every issuer will be traversed through.
|
||||
if (tesSUCCESS == terStatus)
|
||||
terStatus = pushNode (
|
||||
!!uMaxCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uSenderID,
|
||||
uMaxCurrencyID, // Max specifes the currency.
|
||||
uSenderIssuerID);
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: pushed: account=%s currency=%s issuer=%s")
|
||||
% RippleAddress::createHumanAccountID (uSenderID)
|
||||
% STAmount::createHumanCurrency (uMaxCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uSenderIssuerID));
|
||||
|
||||
if (tesSUCCESS == terStatus
|
||||
&& uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender.
|
||||
{
|
||||
// May have an implied account node.
|
||||
// - If it was XRP, then issuers would have matched.
|
||||
|
||||
// Figure out next node properties for implied node.
|
||||
const uint160 uNxtCurrencyID = spSourcePath.size ()
|
||||
? spSourcePath.getElement (0).getCurrency () // Use next node.
|
||||
: uOutCurrencyID; // Use send.
|
||||
const uint160 uNxtAccountID = spSourcePath.size ()
|
||||
? spSourcePath.getElement (0).getAccountID ()
|
||||
: !!uOutCurrencyID
|
||||
? uOutIssuerID == uReceiverID
|
||||
? uReceiverID
|
||||
: uOutIssuerID // Use implied node.
|
||||
: ACCOUNT_XRP;
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: implied check: uMaxIssuerID=%s uSenderIssuerID=%s uNxtCurrencyID=%s uNxtAccountID=%s")
|
||||
% RippleAddress::createHumanAccountID (uMaxIssuerID)
|
||||
% RippleAddress::createHumanAccountID (uSenderIssuerID)
|
||||
% STAmount::createHumanCurrency (uNxtCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uNxtAccountID));
|
||||
|
||||
// Can't just use push implied, because it can't compensate for next account.
|
||||
if (!uNxtCurrencyID // Next is XRP, offer next. Must go through issuer.
|
||||
|| uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next...
|
||||
|| uMaxIssuerID != uNxtAccountID) // Next is not implied issuer
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: sender implied: account=%s currency=%s issuer=%s")
|
||||
% RippleAddress::createHumanAccountID (uMaxIssuerID)
|
||||
% STAmount::createHumanCurrency (uMaxCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uMaxIssuerID));
|
||||
// Add account implied by SendMax.
|
||||
terStatus = pushNode (
|
||||
!!uMaxCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uMaxIssuerID,
|
||||
uMaxCurrencyID,
|
||||
uMaxIssuerID);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FOREACH (const STPathElement & speElement, spSourcePath)
|
||||
{
|
||||
if (tesSUCCESS == terStatus)
|
||||
{
|
||||
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("setExpanded: element in path:"));
|
||||
terStatus = pushNode (speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ());
|
||||
}
|
||||
}
|
||||
|
||||
const Node& pnPrv = vpnNodes.back ();
|
||||
|
||||
if (tesSUCCESS == terStatus
|
||||
&& !!uOutCurrencyID // Next is not XRP
|
||||
&& uOutIssuerID != uReceiverID // Out issuer is not receiver
|
||||
&& (pnPrv.uCurrencyID != uOutCurrencyID // Previous will be an offer.
|
||||
|| pnPrv.uAccountID != uOutIssuerID)) // Need the implied issuer.
|
||||
{
|
||||
// Add implied account.
|
||||
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: receiver implied: account=%s currency=%s issuer=%s")
|
||||
% RippleAddress::createHumanAccountID (uOutIssuerID)
|
||||
% STAmount::createHumanCurrency (uOutCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uOutIssuerID));
|
||||
terStatus = pushNode (
|
||||
!!uOutCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uOutIssuerID,
|
||||
uOutCurrencyID,
|
||||
uOutIssuerID);
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terStatus)
|
||||
{
|
||||
// Create receiver node.
|
||||
// Last node is always an account.
|
||||
|
||||
terStatus = pushNode (
|
||||
!!uOutCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uReceiverID, // Receive to output
|
||||
uOutCurrencyID, // Desired currency
|
||||
uReceiverID);
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terStatus)
|
||||
{
|
||||
// Look for first mention of source in nodes and detect loops.
|
||||
// Note: The output is not allowed to be a source.
|
||||
|
||||
const unsigned int uNodes = vpnNodes.size ();
|
||||
|
||||
for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode)
|
||||
{
|
||||
const Node& pnCur = vpnNodes[uNode];
|
||||
|
||||
if (!umForward.insert (std::make_pair (boost::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second)
|
||||
{
|
||||
// Failed to insert. Have a loop.
|
||||
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: loop detected: %s")
|
||||
% getJson ());
|
||||
|
||||
terStatus = temBAD_PATH_LOOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: in=%s/%s out=%s/%s %s")
|
||||
% STAmount::createHumanCurrency (uMaxCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uMaxIssuerID)
|
||||
% STAmount::createHumanCurrency (uOutCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uOutIssuerID)
|
||||
% getJson ());
|
||||
}
|
||||
|
||||
// Set to a canonical path.
|
||||
// - Remove extra elements
|
||||
// - Assumes path is expanded.
|
||||
//
|
||||
// We do canonicalization to:
|
||||
// - Prevent waste in the ledger.
|
||||
// - Allow longer paths to be specified than would otherwise be allowed.
|
||||
//
|
||||
// Optimization theory:
|
||||
// - Can omit elements that the expansion routine derives.
|
||||
// - Can pack some elements into other elements.
|
||||
//
|
||||
// Rules:
|
||||
// - SendMax if not specified, defaults currency to send and if not sending XRP defaults issuer to sender.
|
||||
// - All paths start with the sender account.
|
||||
// - Currency and issuer is from SendMax.
|
||||
// - All paths end with the destination account.
|
||||
//
|
||||
// Optimization:
|
||||
// - An XRP output implies an offer node or destination node is next.
|
||||
// - A change in currency implies an offer node.
|
||||
// - A change in issuer...
|
||||
void PathState::setCanonical (
|
||||
const PathState& psExpanded
|
||||
)
|
||||
{
|
||||
assert (false);
|
||||
saInAct = psExpanded.saInAct;
|
||||
saOutAct = psExpanded.saOutAct;
|
||||
|
||||
const uint160 uMaxCurrencyID = saInAct.getCurrency ();
|
||||
const uint160 uMaxIssuerID = saInAct.getIssuer ();
|
||||
|
||||
const uint160 uOutCurrencyID = saOutAct.getCurrency ();
|
||||
const uint160 uOutIssuerID = saOutAct.getIssuer ();
|
||||
|
||||
unsigned int uNode = 0;
|
||||
|
||||
unsigned int uEnd = psExpanded.vpnNodes.size (); // The node, indexed by 0, not to include.
|
||||
|
||||
uint160 uDstAccountID = psExpanded.vpnNodes[uEnd].uAccountID; // FIXME: This can't be right
|
||||
|
||||
uint160 uAccountID = psExpanded.vpnNodes[0].uAccountID;
|
||||
uint160 uCurrencyID = uMaxCurrencyID;
|
||||
uint160 uIssuerID = uMaxIssuerID;
|
||||
|
||||
// Node 0 is a composite of the sending account and saInAct.
|
||||
++uNode; // skip node 0
|
||||
|
||||
// Last node is implied: Always skip last node
|
||||
--uEnd; // skip last node
|
||||
|
||||
// saInAct
|
||||
// - currency is always the same as vpnNodes[0].
|
||||
#if 1
|
||||
|
||||
if (uNode != uEnd && uMaxIssuerID != uAccountID)
|
||||
{
|
||||
// saInAct issuer is not the sender. This forces an implied node.
|
||||
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: uNode=%d uEnd=%d") % uNode % uEnd);
|
||||
|
||||
// skip node 1
|
||||
|
||||
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
|
||||
|
||||
++uNode;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
if (uNode != uEnd)
|
||||
{
|
||||
// Have another node
|
||||
bool bKeep = false;
|
||||
|
||||
if (uMaxIssuerID != uAccountID)
|
||||
{
|
||||
}
|
||||
|
||||
if (uMaxCurrencyID) // Not sending XRP.
|
||||
{
|
||||
// Node 1 must be an account.
|
||||
|
||||
if (uMaxIssuerID != uAccountID)
|
||||
{
|
||||
// Node 1 is required to specify issuer.
|
||||
|
||||
bKeep = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Node 1 must be an account
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Node 1 must be an order book.
|
||||
|
||||
bKeep = true;
|
||||
}
|
||||
|
||||
if (bKeep)
|
||||
{
|
||||
uCurrencyID = psExpanded.vpnNodes[uNode].uCurrencyID;
|
||||
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
|
||||
++uNode; // Keep it.
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID)
|
||||
{
|
||||
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: uNode=%d uEnd=%d") % uNode % uEnd);
|
||||
// The next to last node is saOutAct if an issuer different from receiver is supplied.
|
||||
// The next to last node can be implied.
|
||||
|
||||
--uEnd;
|
||||
}
|
||||
|
||||
const Node& pnEnd = psExpanded.vpnNodes[uEnd];
|
||||
|
||||
if (uNode != uEnd
|
||||
&& !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID)
|
||||
{
|
||||
// The current end node is an offer converting to saOutAct's currency and issuer and can be implied.
|
||||
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: uNode=%d uEnd=%d") % uNode % uEnd);
|
||||
|
||||
--uEnd;
|
||||
}
|
||||
|
||||
// Do not include uEnd.
|
||||
for (; uNode != uEnd; ++uNode)
|
||||
{
|
||||
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: uNode=%d uEnd=%d") % uNode % uEnd);
|
||||
const Node& pnPrv = psExpanded.vpnNodes[uNode - 1];
|
||||
const Node& pnCur = psExpanded.vpnNodes[uNode];
|
||||
const Node& pnNxt = psExpanded.vpnNodes[uNode + 1];
|
||||
|
||||
const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
|
||||
|
||||
bool bSkip = false;
|
||||
|
||||
if (bCurAccount)
|
||||
{
|
||||
// Currently at an account.
|
||||
|
||||
// Output is non-XRP and issuer is account.
|
||||
if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID)
|
||||
{
|
||||
// Account issues itself.
|
||||
// XXX Not good enough. Previous account must mention it.
|
||||
|
||||
bSkip = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Currently at an offer.
|
||||
const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
|
||||
const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount);
|
||||
|
||||
if (bPrvAccount && bNxtAccount // Offer surrounded by accounts.
|
||||
&& pnPrv.uCurrencyID != pnNxt.uCurrencyID)
|
||||
{
|
||||
// Offer can be implied by currency change.
|
||||
// XXX What about issuer?
|
||||
|
||||
bSkip = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bSkip)
|
||||
{
|
||||
// Copy node
|
||||
Node pnNew;
|
||||
|
||||
bool bSetAccount = bCurAccount;
|
||||
bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID;
|
||||
// XXX What if we need the next account because we want to skip it?
|
||||
bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID;
|
||||
|
||||
pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0)
|
||||
| (bSetCurrency ? STPathElement::typeCurrency : 0)
|
||||
| (bSetIssuer ? STPathElement::typeIssuer : 0);
|
||||
|
||||
if (bSetAccount)
|
||||
pnNew.uAccountID = pnCur.uAccountID;
|
||||
|
||||
if (bSetCurrency)
|
||||
{
|
||||
pnNew.uCurrencyID = pnCur.uCurrencyID;
|
||||
uCurrencyID = pnNew.uCurrencyID;
|
||||
}
|
||||
|
||||
if (bSetIssuer)
|
||||
pnNew.uIssuerID = pnCur.uIssuerID;
|
||||
|
||||
// XXX ^^^ What about setting uIssuerID?
|
||||
|
||||
if (bSetCurrency && !uCurrencyID)
|
||||
uIssuerID.zero ();
|
||||
|
||||
vpnNodes.push_back (pnNew);
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setCanonical: in=%s/%s out=%s/%s %s")
|
||||
% STAmount::createHumanCurrency (uMaxCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uMaxIssuerID)
|
||||
% STAmount::createHumanCurrency (uOutCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (uOutIssuerID)
|
||||
% getJson ());
|
||||
}
|
||||
|
||||
// This is for debugging not end users. Output names can be changed without warning.
|
||||
Json::Value PathState::getJson () const
|
||||
{
|
||||
Json::Value jvPathState (Json::objectValue);
|
||||
Json::Value jvNodes (Json::arrayValue);
|
||||
|
||||
BOOST_FOREACH (const Node & pnNode, vpnNodes)
|
||||
{
|
||||
jvNodes.append (pnNode.getJson ());
|
||||
}
|
||||
|
||||
jvPathState["status"] = terStatus;
|
||||
jvPathState["index"] = mIndex;
|
||||
jvPathState["nodes"] = jvNodes;
|
||||
|
||||
if (saInReq)
|
||||
jvPathState["in_req"] = saInReq.getJson (0);
|
||||
|
||||
if (saInAct)
|
||||
jvPathState["in_act"] = saInAct.getJson (0);
|
||||
|
||||
if (saInPass)
|
||||
jvPathState["in_pass"] = saInPass.getJson (0);
|
||||
|
||||
if (saOutReq)
|
||||
jvPathState["out_req"] = saOutReq.getJson (0);
|
||||
|
||||
if (saOutAct)
|
||||
jvPathState["out_act"] = saOutAct.getJson (0);
|
||||
|
||||
if (saOutPass)
|
||||
jvPathState["out_pass"] = saOutPass.getJson (0);
|
||||
|
||||
if (uQuality)
|
||||
jvPathState["uQuality"] = boost::str (boost::format ("%d") % uQuality);
|
||||
|
||||
return jvPathState;
|
||||
}
|
||||
|
||||
164
src/ripple_app/paths/ripple_PathState.h
Normal file
164
src/ripple_app/paths/ripple_PathState.h
Normal file
@@ -0,0 +1,164 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PATHSTATE_H
|
||||
#define RIPPLE_PATHSTATE_H
|
||||
|
||||
// account id, currency id, issuer id :: node
|
||||
typedef boost::tuple <uint160, uint160, uint160> aciSource;
|
||||
typedef boost::unordered_map <aciSource, unsigned int> curIssuerNode; // Map of currency, issuer to node index.
|
||||
typedef boost::unordered_map <aciSource, unsigned int>::const_iterator curIssuerNodeConstIterator;
|
||||
|
||||
extern std::size_t hash_value (const aciSource& asValue);
|
||||
|
||||
// Holds a path state under incremental application.
|
||||
class PathState
|
||||
{
|
||||
public:
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
bool operator == (Node const& pnOther) const;
|
||||
|
||||
Json::Value getJson () const;
|
||||
|
||||
public:
|
||||
uint16 uFlags; // --> From path.
|
||||
|
||||
uint160 uAccountID; // --> Accounts: Recieving/sending account.
|
||||
uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send.
|
||||
// --- For offer's next has currency out.
|
||||
uint160 uIssuerID; // --> Currency's issuer
|
||||
|
||||
STAmount saTransferRate; // Transfer rate for uIssuerID.
|
||||
|
||||
// Computed by Reverse.
|
||||
STAmount saRevRedeem; // <-- Amount to redeem to next.
|
||||
STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs.
|
||||
// Issue isn't used by offers.
|
||||
STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee.
|
||||
|
||||
// Computed by forward.
|
||||
STAmount saFwdRedeem; // <-- Amount node will redeem to next.
|
||||
STAmount saFwdIssue; // <-- Amount node will issue to next.
|
||||
// Issue isn't used by offers.
|
||||
STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee.
|
||||
|
||||
// For offers:
|
||||
|
||||
STAmount saRateMax;
|
||||
|
||||
// Directory
|
||||
uint256 uDirectTip; // Current directory.
|
||||
uint256 uDirectEnd; // Next order book.
|
||||
bool bDirectAdvance; // Need to advance directory.
|
||||
SLE::pointer sleDirectDir;
|
||||
STAmount saOfrRate; // For correct ratio.
|
||||
|
||||
// PaymentNode
|
||||
bool bEntryAdvance; // Need to advance entry.
|
||||
unsigned int uEntry;
|
||||
uint256 uOfferIndex;
|
||||
SLE::pointer sleOffer;
|
||||
uint160 uOfrOwnerID;
|
||||
bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets.
|
||||
STAmount saOfferFunds;
|
||||
STAmount saTakerPays;
|
||||
STAmount saTakerGets;
|
||||
|
||||
};
|
||||
public:
|
||||
typedef boost::shared_ptr<PathState> pointer;
|
||||
typedef const boost::shared_ptr<PathState>& ref;
|
||||
|
||||
public:
|
||||
PathState* setIndex (const int iIndex)
|
||||
{
|
||||
mIndex = iIndex;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
int getIndex ()
|
||||
{
|
||||
return mIndex;
|
||||
};
|
||||
|
||||
PathState (
|
||||
const STAmount& saSend,
|
||||
const STAmount& saSendMax)
|
||||
: saInReq (saSendMax)
|
||||
, saOutReq (saSend)
|
||||
{
|
||||
}
|
||||
|
||||
PathState (const PathState& psSrc,
|
||||
bool bUnused)
|
||||
: saInReq (psSrc.saInReq)
|
||||
, saOutReq (psSrc.saOutReq)
|
||||
{
|
||||
}
|
||||
|
||||
void setExpanded (
|
||||
const LedgerEntrySet& lesSource,
|
||||
const STPath& spSourcePath,
|
||||
const uint160& uReceiverID,
|
||||
const uint160& uSenderID
|
||||
);
|
||||
|
||||
void setCanonical (
|
||||
const PathState& psExpanded
|
||||
);
|
||||
|
||||
Json::Value getJson () const;
|
||||
|
||||
#if 0
|
||||
static PathState::pointer createCanonical (
|
||||
PathState& ref pspExpanded
|
||||
)
|
||||
{
|
||||
PathState::pointer pspNew = boost::make_shared<PathState> (pspExpanded->saOutAct, pspExpanded->saInAct);
|
||||
|
||||
pspNew->setCanonical (pspExpanded);
|
||||
|
||||
return pspNew;
|
||||
}
|
||||
#endif
|
||||
static bool lessPriority (PathState& lhs, PathState& rhs);
|
||||
|
||||
public:
|
||||
TER terStatus;
|
||||
std::vector<Node> vpnNodes;
|
||||
|
||||
// When processing, don't want to complicate directory walking with deletion.
|
||||
std::vector<uint256> vUnfundedBecame; // Offers that became unfunded or were completely consumed.
|
||||
|
||||
// First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be
|
||||
// used there.
|
||||
curIssuerNode umForward; // Map of currency, issuer to node index.
|
||||
|
||||
// First time working in reverse a funding source was used.
|
||||
// Source may only be used there if not mentioned by an account.
|
||||
curIssuerNode umReverse; // Map of currency, issuer to node index.
|
||||
|
||||
LedgerEntrySet lesEntries;
|
||||
|
||||
int mIndex; // Index/rank amoung siblings.
|
||||
uint64 uQuality; // 0 = no quality/liquity left.
|
||||
const STAmount& saInReq; // --> Max amount to spend by sender.
|
||||
STAmount saInAct; // --> Amount spent by sender so far.
|
||||
STAmount saInPass; // <-- Amount spent by sender.
|
||||
const STAmount& saOutReq; // --> Amount to send.
|
||||
STAmount saOutAct; // --> Amount actually sent so far.
|
||||
STAmount saOutPass; // <-- Amount actually sent.
|
||||
bool bConsumed; // If true, use consumes full liquidity. False, may or may not.
|
||||
|
||||
private:
|
||||
TER pushNode (const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
|
||||
TER pushImply (const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
|
||||
};
|
||||
|
||||
#endif
|
||||
854
src/ripple_app/paths/ripple_Pathfinder.cpp
Normal file
854
src/ripple_app/paths/ripple_Pathfinder.cpp
Normal file
@@ -0,0 +1,854 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (Pathfinder)
|
||||
|
||||
/*
|
||||
we just need to find a succession of the highest quality paths there until we find enough width
|
||||
|
||||
Don't do branching within each path
|
||||
|
||||
We have a list of paths we are working on but how do we compare the ones that are terminating in a different currency?
|
||||
|
||||
Loops
|
||||
|
||||
TODO: what is a good way to come up with multiple paths?
|
||||
Maybe just change the sort criteria?
|
||||
first a low cost one and then a fat short one?
|
||||
|
||||
|
||||
OrderDB:
|
||||
getXRPOffers();
|
||||
|
||||
// return list of all orderbooks that want XRP
|
||||
// return list of all orderbooks that want IssuerID
|
||||
// return list of all orderbooks that want this issuerID and currencyID
|
||||
*/
|
||||
|
||||
/*
|
||||
Test sending to XRP
|
||||
Test XRP to XRP
|
||||
Test offer in middle
|
||||
Test XRP to USD
|
||||
Test USD to EUR
|
||||
*/
|
||||
|
||||
// we sort the options by:
|
||||
// cost of path
|
||||
// length of path
|
||||
// width of path
|
||||
// correct currency at the end
|
||||
|
||||
// quality, length, liquidity, index
|
||||
typedef boost::tuple<uint64, int, STAmount, unsigned int> path_LQ_t;
|
||||
|
||||
// Lower numbers have better quality. Sort higher quality first.
|
||||
static bool bQualityCmp (const path_LQ_t& a, const path_LQ_t& b)
|
||||
{
|
||||
// 1) Higher quality (lower cost) is better
|
||||
if (a.get<0> () != b.get<0> ())
|
||||
return a.get<0> () < b.get<0> ();
|
||||
|
||||
// 2) More liquidity (higher volume) is better
|
||||
if (a.get<2> () != b.get<2> ())
|
||||
return a.get<2> () > b.get<2> ();
|
||||
|
||||
// 3) Shorter paths are better
|
||||
if (a.get<1> () != b.get<1> ())
|
||||
return a.get<1> () < b.get<1> ();
|
||||
|
||||
// 4) Tie breaker
|
||||
return a.get<3> () > b.get<3> ();
|
||||
}
|
||||
|
||||
typedef std::pair<int, uint160> candidate_t;
|
||||
static bool candCmp (uint32 seq, const candidate_t& first, const candidate_t& second)
|
||||
{
|
||||
if (first.first < second.first)
|
||||
return false;
|
||||
|
||||
if (first.first > second.first)
|
||||
return true;
|
||||
|
||||
return (first.first ^ seq) < (second.first ^ seq);
|
||||
}
|
||||
|
||||
Pathfinder::Pathfinder (RippleLineCache::ref cache,
|
||||
const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID,
|
||||
const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount, bool& bValid)
|
||||
: mSrcAccountID (uSrcAccountID.getAccountID ()),
|
||||
mDstAccountID (uDstAccountID.getAccountID ()),
|
||||
mDstAmount (saDstAmount),
|
||||
mSrcCurrencyID (uSrcCurrencyID),
|
||||
mSrcIssuerID (uSrcIssuerID),
|
||||
mSrcAmount (uSrcCurrencyID, uSrcIssuerID, 1u, 0, true),
|
||||
mLedger (cache->getLedger ()), mRLCache (cache)
|
||||
{
|
||||
|
||||
if (((mSrcAccountID == mDstAccountID) && (mSrcCurrencyID == mDstAmount.getCurrency ())) || mDstAmount.isZero ())
|
||||
{
|
||||
// no need to send to same account with same currency, must send non-zero
|
||||
bValid = false;
|
||||
mLedger.reset ();
|
||||
return;
|
||||
}
|
||||
|
||||
bValid = true;
|
||||
|
||||
// FIXME: This is not right
|
||||
getApp().getOrderBookDB ().setup (mLedger);
|
||||
|
||||
m_loadEvent = getApp().getJobQueue ().getLoadEvent (jtPATH_FIND, "FindPath");
|
||||
|
||||
bool bIssuer = mSrcCurrencyID.isNonZero() && mSrcIssuerID.isNonZero() && (mSrcIssuerID != mSrcAccountID);
|
||||
mSource = STPathElement( // Where does an empty path start?
|
||||
bIssuer ? mSrcIssuerID : mSrcAccountID, // On the source account or issuer account
|
||||
mSrcCurrencyID, // In the source currency
|
||||
mSrcCurrencyID.isZero() ? uint160() : (bIssuer ? mSrcIssuerID : mSrcAccountID));
|
||||
|
||||
}
|
||||
|
||||
bool Pathfinder::findPaths (int iLevel, const unsigned int iMaxPaths, STPathSet& pathsOut)
|
||||
{ // pathsOut contains only non-default paths without source or destiation
|
||||
// On input, pathsOut contains any paths you want to ensure are included if still good
|
||||
|
||||
WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths> mSrcAccountID=%s mDstAccountID=%s mDstAmount=%s mSrcCurrencyID=%s mSrcIssuerID=%s")
|
||||
% RippleAddress::createHumanAccountID (mSrcAccountID)
|
||||
% RippleAddress::createHumanAccountID (mDstAccountID)
|
||||
% mDstAmount.getFullText ()
|
||||
% STAmount::createHumanCurrency (mSrcCurrencyID)
|
||||
% RippleAddress::createHumanAccountID (mSrcIssuerID)
|
||||
);
|
||||
|
||||
if (!mLedger)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << "findPaths< no ledger";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bSrcXrp = mSrcCurrencyID.isZero();
|
||||
bool bDstXrp = mDstAmount.getCurrency().isZero();
|
||||
|
||||
SLE::pointer sleSrc = mLedger->getSLEi(Ledger::getAccountRootIndex(mSrcAccountID));
|
||||
if (!sleSrc)
|
||||
return false;
|
||||
|
||||
SLE::pointer sleDest = mLedger->getSLEi(Ledger::getAccountRootIndex(mDstAccountID));
|
||||
if (!sleDest && (!bDstXrp || (mDstAmount < mLedger->getReserve(0))))
|
||||
return false;
|
||||
|
||||
PaymentType paymentType;
|
||||
if (bSrcXrp && bDstXrp)
|
||||
{ // XRP -> XRP
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "XRP to XRP payment";
|
||||
paymentType = pt_XRP_to_XRP;
|
||||
|
||||
}
|
||||
else if (bSrcXrp)
|
||||
{ // XRP -> non-XRP
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "XRP to non-XRP payment";
|
||||
paymentType = pt_XRP_to_nonXRP;
|
||||
|
||||
}
|
||||
else if (bDstXrp)
|
||||
{ // non-XRP -> XRP
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "non-XRP to XRP payment";
|
||||
paymentType = pt_nonXRP_to_XRP;
|
||||
|
||||
}
|
||||
else if (mSrcCurrencyID == mDstAmount.getCurrency())
|
||||
{ // non-XRP -> non-XRP - Same currency
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "non-XRP to non-XRP - same currency";
|
||||
paymentType = pt_nonXRP_to_same;
|
||||
|
||||
}
|
||||
else
|
||||
{ // non-XRP to non-XRP - Different currency
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "non-XRP to non-XRP - cross currency";
|
||||
paymentType = pt_nonXRP_to_nonXRP;
|
||||
|
||||
}
|
||||
|
||||
BOOST_FOREACH(CostedPath_t const& costedPath, mPathTable[paymentType])
|
||||
{
|
||||
if (costedPath.first <= iLevel)
|
||||
{
|
||||
getPaths(costedPath.second);
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << mCompletePaths.size() << " complete paths found";
|
||||
|
||||
BOOST_FOREACH(const STPath& path, pathsOut)
|
||||
{ // make sure no paths were lost
|
||||
bool found = false;
|
||||
BOOST_FOREACH(const STPath& ePath, mCompletePaths)
|
||||
{
|
||||
if (ePath == path)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
mCompletePaths.addPath(path);
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << mCompletePaths.size() << " paths to filter";
|
||||
|
||||
if (mCompletePaths.size() > iMaxPaths)
|
||||
pathsOut = filterPaths(iMaxPaths);
|
||||
else
|
||||
pathsOut = mCompletePaths;
|
||||
|
||||
return true; // Even if we find no paths, default paths may work, and we don't check them currently
|
||||
}
|
||||
|
||||
STPathSet Pathfinder::filterPaths(int iMaxPaths)
|
||||
{
|
||||
if (mCompletePaths.size() <= iMaxPaths)
|
||||
return mCompletePaths;
|
||||
|
||||
STAmount remaining = mDstAmount;
|
||||
|
||||
// must subtract liquidity in default path from remaining amount
|
||||
try
|
||||
{
|
||||
STAmount saMaxAmountAct, saDstAmountAct;
|
||||
std::vector<PathState::pointer> vpsExpanded;
|
||||
LedgerEntrySet lesSandbox (mLedger, tapNONE);
|
||||
|
||||
TER result = RippleCalc::rippleCalc (
|
||||
lesSandbox,
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
mSrcAmount,
|
||||
mDstAmount,
|
||||
mDstAccountID,
|
||||
mSrcAccountID,
|
||||
STPathSet (),
|
||||
true, // allow partial payment
|
||||
false,
|
||||
false, // don't suppress default paths, that's the point
|
||||
true);
|
||||
|
||||
if (tesSUCCESS == result)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << "Default path contributes: " << saDstAmountAct;
|
||||
remaining -= saDstAmountAct;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << "Default path fails: " << transToken (result);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << "Default path causes exception";
|
||||
}
|
||||
|
||||
std::vector<path_LQ_t> vMap;
|
||||
|
||||
// Build map of quality to entry.
|
||||
for (int i = mCompletePaths.size (); i--;)
|
||||
{
|
||||
STAmount saMaxAmountAct;
|
||||
STAmount saDstAmountAct;
|
||||
std::vector<PathState::pointer> vpsExpanded;
|
||||
STPathSet spsPaths;
|
||||
STPath& spCurrent = mCompletePaths[i];
|
||||
|
||||
spsPaths.addPath (spCurrent); // Just checking the current path.
|
||||
|
||||
TER terResult;
|
||||
|
||||
try
|
||||
{
|
||||
LedgerEntrySet lesSandbox (mLedger, tapNONE);
|
||||
|
||||
terResult = RippleCalc::rippleCalc (
|
||||
lesSandbox,
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
mSrcAmount, // --> amount to send max.
|
||||
mDstAmount, // --> amount to deliver.
|
||||
mDstAccountID,
|
||||
mSrcAccountID,
|
||||
spsPaths,
|
||||
true, // --> bPartialPayment: Allow, it might contribute.
|
||||
false, // --> bLimitQuality: Assume normal transaction.
|
||||
true, // --> bNoRippleDirect: Providing the only path.
|
||||
true); // --> bStandAlone: Don't need to delete unfundeds.
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
WriteLog (lsINFO, Pathfinder) << "findPaths: Caught throw: " << e.what ();
|
||||
|
||||
terResult = tefEXCEPTION;
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
uint64 uQuality = STAmount::getRate (saDstAmountAct, saMaxAmountAct);
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder)
|
||||
<< boost::str (boost::format ("findPaths: quality: %d: %s")
|
||||
% uQuality
|
||||
% spCurrent.getJson (0));
|
||||
|
||||
vMap.push_back (path_LQ_t (uQuality, spCurrent.mPath.size (), saDstAmountAct, i));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder)
|
||||
<< boost::str (boost::format ("findPaths: dropping: %s: %s")
|
||||
% transToken (terResult)
|
||||
% spCurrent.getJson (0));
|
||||
}
|
||||
}
|
||||
|
||||
STPathSet spsDst;
|
||||
|
||||
if (vMap.size())
|
||||
{
|
||||
std::sort (vMap.begin (), vMap.end (), bQualityCmp); // Lower is better and should be first.
|
||||
|
||||
|
||||
for (int i = 0, iPathsLeft = iMaxPaths; (iPathsLeft > 0) && (i < vMap.size ()); ++i)
|
||||
{
|
||||
path_LQ_t& lqt = vMap[i];
|
||||
|
||||
if ((iPathsLeft != 1) || (lqt.get<2> () >= remaining))
|
||||
{
|
||||
// last path must fill
|
||||
--iPathsLeft;
|
||||
remaining -= lqt.get<2> ();
|
||||
spsDst.addPath (mCompletePaths[lqt.get<3> ()]);
|
||||
}
|
||||
else
|
||||
WriteLog (lsDEBUG, Pathfinder) << "Skipping a non-filling path: " << mCompletePaths[lqt.get<3> ()].getJson (0);
|
||||
}
|
||||
|
||||
if (remaining.isPositive ())
|
||||
{
|
||||
WriteLog (lsINFO, Pathfinder) << "Paths could not send " << remaining << " of " << mDstAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: RESULTS: %s") % spsDst.getJson (0));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: RESULTS: non-defaults filtered away"));
|
||||
}
|
||||
|
||||
return spsDst;
|
||||
}
|
||||
|
||||
boost::unordered_set<uint160> usAccountSourceCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
|
||||
bool includeXRP)
|
||||
{
|
||||
boost::unordered_set<uint160> usCurrencies;
|
||||
|
||||
// YYY Only bother if they are above reserve
|
||||
if (includeXRP)
|
||||
usCurrencies.insert (uint160 (CURRENCY_XRP));
|
||||
|
||||
// List of ripple lines.
|
||||
AccountItems rippleLines (raAccountID.getAccountID (), lrLedger, AccountItem::pointer (new RippleState ()));
|
||||
|
||||
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
|
||||
{
|
||||
RippleState* rspEntry = (RippleState*) item.get ();
|
||||
const STAmount& saBalance = rspEntry->getBalance ();
|
||||
|
||||
// Filter out non
|
||||
if (saBalance.isPositive () // Have IOUs to send.
|
||||
|| (rspEntry->getLimitPeer () // Peer extends credit.
|
||||
&& ((-saBalance) < rspEntry->getLimitPeer ()))) // Credit left.
|
||||
{
|
||||
usCurrencies.insert (saBalance.getCurrency ());
|
||||
}
|
||||
}
|
||||
|
||||
usCurrencies.erase (CURRENCY_BAD);
|
||||
return usCurrencies;
|
||||
}
|
||||
|
||||
boost::unordered_set<uint160> usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
|
||||
bool includeXRP)
|
||||
{
|
||||
boost::unordered_set<uint160> usCurrencies;
|
||||
|
||||
if (includeXRP)
|
||||
usCurrencies.insert (uint160 (CURRENCY_XRP)); // Even if account doesn't exist
|
||||
|
||||
// List of ripple lines.
|
||||
AccountItems rippleLines (raAccountID.getAccountID (), lrLedger, AccountItem::pointer (new RippleState ()));
|
||||
|
||||
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
|
||||
{
|
||||
RippleState* rspEntry = (RippleState*) item.get ();
|
||||
const STAmount& saBalance = rspEntry->getBalance ();
|
||||
|
||||
if (saBalance < rspEntry->getLimit ()) // Can take more
|
||||
usCurrencies.insert (saBalance.getCurrency ());
|
||||
}
|
||||
|
||||
usCurrencies.erase (CURRENCY_BAD);
|
||||
return usCurrencies;
|
||||
}
|
||||
|
||||
bool Pathfinder::matchesOrigin (const uint160& currency, const uint160& issuer)
|
||||
{
|
||||
if (currency != mSrcCurrencyID)
|
||||
return false;
|
||||
|
||||
if (currency.isZero())
|
||||
return true;
|
||||
|
||||
return (issuer == mSrcIssuerID) || (issuer == mSrcAccountID);
|
||||
}
|
||||
|
||||
int Pathfinder::getPathsOut (const uint160& currencyID, const uint160& accountID,
|
||||
bool isDstCurrency, const uint160& dstAccount)
|
||||
{
|
||||
#ifdef C11X
|
||||
std::pair<const uint160&, const uint160&> accountCurrency (currencyID, accountID);
|
||||
#else
|
||||
std::pair<uint160, uint160> accountCurrency (currencyID, accountID);
|
||||
#endif
|
||||
boost::unordered_map<std::pair<uint160, uint160>, int>::iterator it = mPOMap.find (accountCurrency);
|
||||
|
||||
if (it != mPOMap.end ())
|
||||
return it->second;
|
||||
|
||||
int aFlags = mLedger->getSLEi(Ledger::getAccountRootIndex(accountID))->getFieldU32(sfFlags);
|
||||
bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0;
|
||||
|
||||
int count = 0;
|
||||
AccountItems& rippleLines (mRLCache->getRippleLines (accountID));
|
||||
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
|
||||
{
|
||||
RippleState* rspEntry = (RippleState*) item.get ();
|
||||
|
||||
if (currencyID != rspEntry->getLimit ().getCurrency ())
|
||||
nothing ();
|
||||
else if (!rspEntry->getBalance ().isPositive () &&
|
||||
(!rspEntry->getLimitPeer ()
|
||||
|| -rspEntry->getBalance () >= rspEntry->getLimitPeer ()
|
||||
|| (bAuthRequired && !rspEntry->getAuth ())))
|
||||
nothing ();
|
||||
else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ()))
|
||||
count += 10000; // count a path to the destination extra
|
||||
else
|
||||
++count;
|
||||
}
|
||||
mPOMap[accountCurrency] = count;
|
||||
return count;
|
||||
}
|
||||
|
||||
void Pathfinder::addLink(
|
||||
const STPathSet& currentPaths, // The paths to build from
|
||||
STPathSet& incompletePaths, // The set of partial paths we add to
|
||||
int addFlags)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << "addLink< on " << currentPaths.size() << " source(s), flags=" << addFlags;
|
||||
BOOST_FOREACH(const STPath& path, currentPaths)
|
||||
{
|
||||
addLink(path, incompletePaths, addFlags);
|
||||
}
|
||||
}
|
||||
|
||||
STPathSet& Pathfinder::getPaths(PathType_t const& type, bool addComplete)
|
||||
{
|
||||
std::map< PathType_t, STPathSet >::iterator it = mPaths.find(type);
|
||||
|
||||
// We already have these paths
|
||||
if (it != mPaths.end())
|
||||
return it->second;
|
||||
|
||||
// The type is empty
|
||||
if (type.empty())
|
||||
return mPaths[type];
|
||||
|
||||
NodeType toAdd = type.back();
|
||||
PathType_t pathType(type);
|
||||
pathType.pop_back();
|
||||
|
||||
STPathSet pathsIn = getPaths(pathType, false);
|
||||
STPathSet& pathsOut = mPaths[type];
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder)
|
||||
<< "getPaths< adding onto '"
|
||||
<< pathTypeToString(pathType) << "' to get '"
|
||||
<< pathTypeToString(type) << "'";
|
||||
|
||||
int cp = mCompletePaths.size();
|
||||
|
||||
switch (toAdd)
|
||||
{
|
||||
|
||||
case nt_SOURCE:
|
||||
|
||||
{ // source is an empty path
|
||||
assert(pathsOut.isEmpty());
|
||||
pathsOut.addPath(STPath());
|
||||
}
|
||||
break;
|
||||
|
||||
case nt_ACCOUNTS:
|
||||
addLink(pathsIn, pathsOut, afADD_ACCOUNTS);
|
||||
break;
|
||||
|
||||
case nt_BOOKS:
|
||||
addLink(pathsIn, pathsOut, afADD_BOOKS);
|
||||
break;
|
||||
|
||||
case nt_XRP_BOOK:
|
||||
addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_XRP);
|
||||
break;
|
||||
|
||||
case nt_DEST_BOOK:
|
||||
addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_LAST);
|
||||
break;
|
||||
|
||||
case nt_DESTINATION:
|
||||
// FIXME: What if a different issuer was specified on the destination amount
|
||||
addLink(pathsIn, pathsOut, afADD_ACCOUNTS | afAC_LAST);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
CondLog (mCompletePaths.size() != cp, lsDEBUG, Pathfinder)
|
||||
<< (mCompletePaths.size() - cp)
|
||||
<< " complete paths added";
|
||||
WriteLog (lsDEBUG, Pathfinder) << "getPaths> " << pathsOut.size() << " partial paths found";
|
||||
return pathsOut;
|
||||
}
|
||||
|
||||
void Pathfinder::addLink(
|
||||
const STPath& currentPath, // The path to build from
|
||||
STPathSet& incompletePaths, // The set of partial paths we add to
|
||||
int addFlags)
|
||||
{
|
||||
STPathElement const& pathEnd = currentPath.isEmpty() ? mSource : currentPath.mPath.back ();
|
||||
uint160 const& uEndCurrency = pathEnd.mCurrencyID;
|
||||
uint160 const& uEndIssuer = pathEnd.mIssuerID;
|
||||
uint160 const& uEndAccount = pathEnd.mAccountID;
|
||||
bool const bOnXRP = uEndCurrency.isZero();
|
||||
|
||||
WriteLog (lsTRACE, Pathfinder) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP;
|
||||
WriteLog (lsTRACE, Pathfinder) << currentPath.getJson(0);
|
||||
|
||||
if (addFlags & afADD_ACCOUNTS)
|
||||
{ // add accounts
|
||||
if (bOnXRP)
|
||||
{
|
||||
if (mDstAmount.isNative() && !currentPath.isEmpty())
|
||||
{ // non-default path to XRP destination
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found ax: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(currentPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // search for accounts to add
|
||||
SLE::pointer sleEnd = mLedger->getSLEi(Ledger::getAccountRootIndex(uEndAccount));
|
||||
if (sleEnd)
|
||||
{
|
||||
bool const bRequireAuth = isSetBit(sleEnd->getFieldU32(sfFlags), lsfRequireAuth);
|
||||
bool const bIsEndCurrency = (uEndCurrency == mDstAmount.getCurrency());
|
||||
|
||||
AccountItems& rippleLines(mRLCache->getRippleLines(uEndAccount));
|
||||
|
||||
std::vector< std::pair<int, uint160> > candidates;
|
||||
candidates.reserve(rippleLines.getItems().size());
|
||||
|
||||
BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems())
|
||||
{
|
||||
RippleState const& rspEntry = * reinterpret_cast<RippleState const *>(item.get());
|
||||
uint160 const& acctID = rspEntry.getAccountIDPeer();
|
||||
|
||||
if ((uEndCurrency == rspEntry.getLimit().getCurrency()) &&
|
||||
!currentPath.hasSeen(acctID, uEndCurrency, acctID))
|
||||
{ // path is for correct currency and has not been seen
|
||||
if (!rspEntry.getBalance().isPositive()
|
||||
&& (!rspEntry.getLimitPeer()
|
||||
|| -rspEntry.getBalance() >= rspEntry.getLimitPeer()
|
||||
|| (bRequireAuth && !rspEntry.getAuth())))
|
||||
{
|
||||
// path has no credit
|
||||
}
|
||||
else if (acctID == mDstAccountID)
|
||||
{ // destination is always worth trying
|
||||
if (uEndCurrency == mDstAmount.getCurrency())
|
||||
{ // this is a complete path
|
||||
if (!currentPath.isEmpty())
|
||||
{
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found ae: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(currentPath);
|
||||
}
|
||||
}
|
||||
else if ((addFlags & afAC_LAST) == 0)
|
||||
{ // this is a high-priority candidate
|
||||
candidates.push_back(std::make_pair(100000, acctID));
|
||||
}
|
||||
}
|
||||
else if (acctID == mSrcAccountID)
|
||||
{
|
||||
// going back to the source is bad
|
||||
}
|
||||
else if ((addFlags & afAC_LAST) == 0)
|
||||
{ // save this candidate
|
||||
int out = getPathsOut(uEndCurrency, acctID, bIsEndCurrency, mDstAccountID);
|
||||
if (out)
|
||||
candidates.push_back(std::make_pair(out, acctID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates.empty())
|
||||
{
|
||||
std::sort (candidates.begin(), candidates.end(),
|
||||
BIND_TYPE(candCmp, mLedger->getLedgerSeq(), P_1, P_2));
|
||||
|
||||
int count = candidates.size();
|
||||
if ((count > 10) && (uEndAccount != mSrcAccountID)) // allow more paths from source
|
||||
count = 10;
|
||||
else if (count > 50)
|
||||
count = 50;
|
||||
|
||||
std::vector< std::pair<int, uint160> >::const_iterator it = candidates.begin();
|
||||
while (count-- != 0)
|
||||
{ // Add accounts to incompletePaths
|
||||
incompletePaths.assembleAdd(currentPath, STPathElement(STPathElement::typeAccount, it->second, uEndCurrency, it->second));
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog(lsWARNING, Pathfinder) << "Path ends on non-existent issuer";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (addFlags & afADD_BOOKS)
|
||||
{ // add order books
|
||||
if (addFlags & afOB_XRP)
|
||||
{ // to XRP only
|
||||
if (!bOnXRP && getApp().getOrderBookDB().isBookToXRP(uEndIssuer, uEndCurrency))
|
||||
{
|
||||
incompletePaths.assembleAdd(currentPath, STPathElement(STPathElement::typeCurrency, ACCOUNT_XRP, CURRENCY_XRP, ACCOUNT_XRP));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool bDestOnly = (addFlags & afOB_LAST) != 0;
|
||||
std::vector<OrderBook::pointer> books;
|
||||
getApp().getOrderBookDB().getBooksByTakerPays(uEndIssuer, uEndCurrency, books);
|
||||
WriteLog (lsTRACE, Pathfinder) << books.size() << " books found from this currency/issuer";
|
||||
BOOST_FOREACH(OrderBook::ref book, books)
|
||||
{
|
||||
if (!currentPath.hasSeen (ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()) &&
|
||||
!matchesOrigin(book->getCurrencyOut(), book->getIssuerOut()) &&
|
||||
(!bDestOnly || (book->getCurrencyOut() == mDstAmount.getCurrency())))
|
||||
{
|
||||
STPath newPath(currentPath);
|
||||
|
||||
if (book->getCurrencyOut().isZero())
|
||||
{ // to XRP
|
||||
|
||||
// add the order book itself
|
||||
newPath.addElement(STPathElement(STPathElement::typeCurrency, ACCOUNT_XRP, CURRENCY_XRP, ACCOUNT_XRP));
|
||||
|
||||
if (mDstAmount.getCurrency().isZero())
|
||||
{ // destination is XRP, add account and path is complete
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found bx: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(newPath);
|
||||
}
|
||||
else
|
||||
incompletePaths.addPath(newPath);
|
||||
}
|
||||
else if (!currentPath.hasSeen(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut()))
|
||||
{ // Don't want the book if we've already seen the issuer
|
||||
// add the order book itself
|
||||
newPath.addElement(STPathElement(STPathElement::typeCurrency | STPathElement::typeIssuer,
|
||||
ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()));
|
||||
|
||||
if ((book->getIssuerOut() == mDstAccountID) && book->getCurrencyOut() == mDstAmount.getCurrency())
|
||||
{ // with the destination account, this path is complete
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found ba: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(newPath);
|
||||
}
|
||||
else
|
||||
{ // add issuer's account, path still incomplete
|
||||
incompletePaths.assembleAdd(newPath,
|
||||
STPathElement(STPathElement::typeAccount,
|
||||
book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<Pathfinder::PaymentType, Pathfinder::CostedPathList_t> Pathfinder::mPathTable;
|
||||
|
||||
Pathfinder::PathType_t Pathfinder::makePath(char const *string)
|
||||
{
|
||||
PathType_t ret;
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (*string++)
|
||||
{
|
||||
case 's': // source
|
||||
ret.push_back(nt_SOURCE);
|
||||
break;
|
||||
|
||||
case 'a': // accounts
|
||||
ret.push_back(nt_ACCOUNTS);
|
||||
break;
|
||||
|
||||
case 'b': // books
|
||||
ret.push_back(nt_BOOKS);
|
||||
break;
|
||||
|
||||
case 'x': // xrp book
|
||||
ret.push_back(nt_XRP_BOOK);
|
||||
break;
|
||||
|
||||
case 'f': // book to final currency
|
||||
ret.push_back(nt_DEST_BOOK);
|
||||
break;
|
||||
|
||||
case 'd': // destination (with account, if required and not already present)
|
||||
ret.push_back(nt_DESTINATION);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Pathfinder::pathTypeToString(PathType_t const& type)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
BOOST_FOREACH(NodeType const& node, type)
|
||||
{
|
||||
switch (node)
|
||||
{
|
||||
case nt_SOURCE:
|
||||
ret.append("s");
|
||||
break;
|
||||
case nt_ACCOUNTS:
|
||||
ret.append("a");
|
||||
break;
|
||||
case nt_BOOKS:
|
||||
ret.append("b");
|
||||
break;
|
||||
case nt_XRP_BOOK:
|
||||
ret.append("x");
|
||||
break;
|
||||
case nt_DEST_BOOK:
|
||||
ret.append("f");
|
||||
break;
|
||||
case nt_DESTINATION:
|
||||
ret.append("d");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Costs:
|
||||
// 0 = minimum to make some payments possible
|
||||
// 1 = include trivial paths to make common cases work
|
||||
// 4 = normal fast search level
|
||||
// 7 = normal slow search level
|
||||
// 10 = most agressive
|
||||
|
||||
void Pathfinder::initPathTable()
|
||||
{ // CAUTION: Do not include rules that build default paths
|
||||
{ // XRP to XRP
|
||||
CostedPathList_t& list = mPathTable[pt_XRP_to_XRP];
|
||||
|
||||
list.push_back(CostedPath_t(8, makePath("sbxd"))); // source -> book -> book_to_XRP -> destination
|
||||
list.push_back(CostedPath_t(9, makePath("sbaxd"))); // source -> book -> gateway -> to_XRP ->destination
|
||||
}
|
||||
|
||||
{ // XRP to non-XRP
|
||||
CostedPathList_t& list = mPathTable[pt_XRP_to_nonXRP];
|
||||
|
||||
list.push_back(CostedPath_t(0, makePath("sfd"))); // source -> book -> gateway
|
||||
list.push_back(CostedPath_t(3, makePath("sfad"))); // source -> book -> account -> destination
|
||||
list.push_back(CostedPath_t(5, makePath("sfaad"))); // source -> book -> account -> account -> destination
|
||||
list.push_back(CostedPath_t(6, makePath("sbfd"))); // source -> book -> book -> destination
|
||||
list.push_back(CostedPath_t(8, makePath("sbafd"))); // source -> book -> account -> book -> destination
|
||||
list.push_back(CostedPath_t(9, makePath("sbfad"))); // source -> book -> book -> account -> destination
|
||||
list.push_back(CostedPath_t(10, makePath("sbafad")));
|
||||
}
|
||||
|
||||
{ // non-XRP to XRP
|
||||
CostedPathList_t& list = mPathTable[pt_nonXRP_to_XRP];
|
||||
|
||||
list.push_back(CostedPath_t(0, makePath("sxd"))); // gateway buys XRP
|
||||
list.push_back(CostedPath_t(1, makePath("saxd"))); // source -> gateway -> book(XRP) -> dest
|
||||
list.push_back(CostedPath_t(6, makePath("saaxd")));
|
||||
list.push_back(CostedPath_t(7, makePath("sbxd")));
|
||||
list.push_back(CostedPath_t(8, makePath("sabxd")));
|
||||
list.push_back(CostedPath_t(9, makePath("sabaxd")));
|
||||
}
|
||||
|
||||
{ // non-XRP to non-XRP (same currency)
|
||||
CostedPathList_t& list = mPathTable[pt_nonXRP_to_same];
|
||||
|
||||
list.push_back(CostedPath_t(1, makePath("sad"))); // source -> gateway -> destination
|
||||
list.push_back(CostedPath_t(1, makePath("sfd"))); // source -> book -> destination
|
||||
list.push_back(CostedPath_t(4, makePath("safd"))); // source -> gateway -> book -> destination
|
||||
list.push_back(CostedPath_t(4, makePath("sfad")));
|
||||
list.push_back(CostedPath_t(5, makePath("saad")));
|
||||
list.push_back(CostedPath_t(5, makePath("sxfd")));
|
||||
list.push_back(CostedPath_t(6, makePath("sxfad")));
|
||||
list.push_back(CostedPath_t(6, makePath("safad")));
|
||||
list.push_back(CostedPath_t(6, makePath("saxfd"))); // source -> gateway -> book to XRP -> book -> destination
|
||||
list.push_back(CostedPath_t(6, makePath("saxfad")));
|
||||
list.push_back(CostedPath_t(8, makePath("saaad")));
|
||||
}
|
||||
|
||||
{ // non-XRP to non-XRP (different currency)
|
||||
CostedPathList_t& list = mPathTable[pt_nonXRP_to_nonXRP];
|
||||
|
||||
list.push_back(CostedPath_t(1, makePath("sfad")));
|
||||
list.push_back(CostedPath_t(1, makePath("safd")));
|
||||
list.push_back(CostedPath_t(3, makePath("safad")));
|
||||
list.push_back(CostedPath_t(4, makePath("sxfd")));
|
||||
list.push_back(CostedPath_t(5, makePath("saxfd")));
|
||||
list.push_back(CostedPath_t(6, makePath("saxfad")));
|
||||
list.push_back(CostedPath_t(7, makePath("saafd")));
|
||||
list.push_back(CostedPath_t(8, makePath("saafad")));
|
||||
list.push_back(CostedPath_t(9, makePath("safaad")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
129
src/ripple_app/paths/ripple_Pathfinder.h
Normal file
129
src/ripple_app/paths/ripple_Pathfinder.h
Normal file
@@ -0,0 +1,129 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PATHFINDER_H
|
||||
#define RIPPLE_PATHFINDER_H
|
||||
|
||||
// VFALCO TODO Remove this unused stuff?
|
||||
#if 0
|
||||
//
|
||||
// This is a very simple implementation. This can be made way better.
|
||||
// We are simply flooding from the start. And doing an exhaustive search of all paths under maxSearchSteps. An easy improvement would
|
||||
be to flood from both directions.
|
||||
//
|
||||
|
||||
class PathOption
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<PathOption> pointer;
|
||||
typedef const boost::shared_ptr<PathOption>& ref;
|
||||
|
||||
STPath mPath;
|
||||
bool mCorrectCurrency; // for the sorting
|
||||
uint160 mCurrencyID; // what currency we currently have at the end of the path
|
||||
uint160 mCurrentAccount; // what account is at the end of the path
|
||||
int mTotalCost; // in send currency
|
||||
STAmount mMinWidth; // in dest currency
|
||||
float mQuality;
|
||||
|
||||
PathOption (uint160& srcAccount, uint160& srcCurrencyID, const uint160& dstCurrencyID);
|
||||
PathOption (PathOption::pointer other);
|
||||
};
|
||||
#endif
|
||||
|
||||
/** Calculates payment paths.
|
||||
|
||||
The @ref RippleCalc determines the quality of the found paths.
|
||||
|
||||
@see RippleCalc
|
||||
*/
|
||||
class Pathfinder
|
||||
{
|
||||
public:
|
||||
Pathfinder (RippleLineCache::ref cache,
|
||||
const RippleAddress& srcAccountID, const RippleAddress& dstAccountID,
|
||||
const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount, bool& bValid);
|
||||
|
||||
static void initPathTable();
|
||||
bool findPaths (int iLevel, const unsigned int iMaxPaths, STPathSet& spsDst);
|
||||
|
||||
private:
|
||||
|
||||
enum PaymentType
|
||||
{
|
||||
pt_XRP_to_XRP,
|
||||
pt_XRP_to_nonXRP,
|
||||
pt_nonXRP_to_XRP,
|
||||
pt_nonXRP_to_same,
|
||||
pt_nonXRP_to_nonXRP
|
||||
};
|
||||
|
||||
enum NodeType
|
||||
{
|
||||
nt_SOURCE, // The source account with an issuer account, if required
|
||||
nt_ACCOUNTS, // Accounts that connect from this source/currency
|
||||
nt_BOOKS, // Order books that connect to this currency
|
||||
nt_XRP_BOOK, // The order book from this currency to XRP
|
||||
nt_DEST_BOOK, // The order book to the destination currency/issuer
|
||||
nt_DESTINATION // The destination account only
|
||||
};
|
||||
|
||||
typedef std::vector<NodeType> PathType_t;
|
||||
typedef std::pair<int, PathType_t> CostedPath_t;
|
||||
typedef std::vector<CostedPath_t> CostedPathList_t;
|
||||
|
||||
// returns true if any building paths are now complete?
|
||||
bool checkComplete (STPathSet& retPathSet);
|
||||
|
||||
static std::string pathTypeToString(PathType_t const&);
|
||||
|
||||
bool matchesOrigin (const uint160& currency, const uint160& issuer);
|
||||
|
||||
int getPathsOut (const uint160& currency, const uint160& accountID,
|
||||
bool isDestCurrency, const uint160& dest);
|
||||
|
||||
void addLink(const STPath& currentPath, STPathSet& incompletePaths, int addFlags);
|
||||
void addLink(const STPathSet& currentPaths, STPathSet& incompletePaths, int addFlags);
|
||||
STPathSet& getPaths(const PathType_t& type, bool addComplete = true);
|
||||
STPathSet filterPaths(int iMaxPaths);
|
||||
|
||||
// Our main table of paths
|
||||
|
||||
static std::map<PaymentType, CostedPathList_t> mPathTable;
|
||||
static PathType_t makePath(char const*);
|
||||
|
||||
uint160 mSrcAccountID;
|
||||
uint160 mDstAccountID;
|
||||
STAmount mDstAmount;
|
||||
uint160 mSrcCurrencyID;
|
||||
uint160 mSrcIssuerID;
|
||||
STAmount mSrcAmount;
|
||||
|
||||
Ledger::pointer mLedger;
|
||||
LoadEvent::pointer m_loadEvent;
|
||||
RippleLineCache::pointer mRLCache;
|
||||
|
||||
STPathElement mSource;
|
||||
STPathSet mCompletePaths;
|
||||
std::map< PathType_t, STPathSet > mPaths;
|
||||
|
||||
boost::unordered_map<uint160, AccountItems::pointer> mRLMap;
|
||||
boost::unordered_map<std::pair<uint160, uint160>, int> mPOMap;
|
||||
|
||||
static const uint32 afADD_ACCOUNTS = 0x001; // Add ripple paths
|
||||
static const uint32 afADD_BOOKS = 0x002; // Add order books
|
||||
static const uint32 afOB_XRP = 0x010; // Add order book to XRP only
|
||||
static const uint32 afOB_LAST = 0x040; // Must link to destination currency
|
||||
static const uint32 afAC_LAST = 0x080; // Destination account only
|
||||
};
|
||||
|
||||
boost::unordered_set<uint160> usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
|
||||
bool includeXRP);
|
||||
|
||||
boost::unordered_set<uint160> usAccountSourceCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
|
||||
bool includeXRP);
|
||||
|
||||
#endif
|
||||
2670
src/ripple_app/paths/ripple_RippleCalc.cpp
Normal file
2670
src/ripple_app/paths/ripple_RippleCalc.cpp
Normal file
File diff suppressed because it is too large
Load Diff
88
src/ripple_app/paths/ripple_RippleCalc.h
Normal file
88
src/ripple_app/paths/ripple_RippleCalc.h
Normal file
@@ -0,0 +1,88 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_RIPPLECALC_H
|
||||
#define RIPPLE_RIPPLECALC_H
|
||||
|
||||
/** Calculate the quality of a payment path.
|
||||
|
||||
The quality is a synonym for price. Specifically, the amount of
|
||||
input required to produce a given output along a specified path.
|
||||
*/
|
||||
// VFALCO TODO What's the difference between a RippleState versus PathState?
|
||||
//
|
||||
class RippleCalc
|
||||
{
|
||||
public:
|
||||
// First time working in reverse a funding source was mentioned. Source may only be used there.
|
||||
curIssuerNode mumSource; // Map of currency, issuer to node index.
|
||||
|
||||
// If the transaction fails to meet some constraint, still need to delete unfunded offers.
|
||||
boost::unordered_set<uint256> musUnfundedFound; // Offers that were found unfunded.
|
||||
|
||||
void pathNext (PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
|
||||
TER calcNode (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeOfferRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeOfferFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeAccountRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeAccountFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeAdvance (const unsigned int uNode, PathState& psCur, const bool bMultiQuality, const bool bReverse);
|
||||
TER calcNodeDeliverRev (
|
||||
const unsigned int uNode,
|
||||
PathState& psCur,
|
||||
const bool bMultiQuality,
|
||||
const uint160& uOutAccountID,
|
||||
const STAmount& saOutReq,
|
||||
STAmount& saOutAct);
|
||||
|
||||
TER calcNodeDeliverFwd (
|
||||
const unsigned int uNode,
|
||||
PathState& psCur,
|
||||
const bool bMultiQuality,
|
||||
const uint160& uInAccountID,
|
||||
const STAmount& saInReq,
|
||||
STAmount& saInAct,
|
||||
STAmount& saInFees);
|
||||
|
||||
void calcNodeRipple (const uint32 uQualityIn, const uint32 uQualityOut,
|
||||
const STAmount& saPrvReq, const STAmount& saCurReq,
|
||||
STAmount& saPrvAct, STAmount& saCurAct,
|
||||
uint64& uRateMax);
|
||||
|
||||
RippleCalc (LedgerEntrySet& lesNodes, const bool bOpenLedger)
|
||||
: lesActive (lesNodes), mOpenLedger (bOpenLedger)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
static TER rippleCalc (
|
||||
LedgerEntrySet& lesActive,
|
||||
STAmount& saMaxAmountAct,
|
||||
STAmount& saDstAmountAct,
|
||||
std::vector<PathState::pointer>& vpsExpanded,
|
||||
const STAmount& saDstAmountReq,
|
||||
const STAmount& saMaxAmountReq,
|
||||
const uint160& uDstAccountID,
|
||||
const uint160& uSrcAccountID,
|
||||
const STPathSet& spsPaths,
|
||||
const bool bPartialPayment,
|
||||
const bool bLimitQuality,
|
||||
const bool bNoRippleDirect,
|
||||
const bool bStandAlone, // --> True, not to affect accounts.
|
||||
const bool bOpenLedger = true // --> What kind of errors to return.
|
||||
);
|
||||
|
||||
static void setCanonical (STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded, bool bKeepDefault);
|
||||
|
||||
protected:
|
||||
LedgerEntrySet& lesActive;
|
||||
bool mOpenLedger;
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
24
src/ripple_app/paths/ripple_RippleLineCache.cpp
Normal file
24
src/ripple_app/paths/ripple_RippleLineCache.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
RippleLineCache::RippleLineCache (Ledger::ref l)
|
||||
: mLock (this, "RippleLineCache", __FILE__, __LINE__)
|
||||
, mLedger (l)
|
||||
{
|
||||
}
|
||||
|
||||
AccountItems& RippleLineCache::getRippleLines (const uint160& accountID)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
boost::unordered_map <uint160, AccountItems::pointer>::iterator it = mRLMap.find (accountID);
|
||||
|
||||
if (it == mRLMap.end ())
|
||||
it = mRLMap.insert (std::make_pair (accountID, boost::make_shared<AccountItems>
|
||||
(boost::cref (accountID), boost::cref (mLedger), AccountItem::pointer (new RippleState ())))).first;
|
||||
|
||||
return *it->second;
|
||||
}
|
||||
36
src/ripple_app/paths/ripple_RippleLineCache.h
Normal file
36
src/ripple_app/paths/ripple_RippleLineCache.h
Normal file
@@ -0,0 +1,36 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_RIPPLELINECACHE_H
|
||||
#define RIPPLE_RIPPLELINECACHE_H
|
||||
|
||||
// Used by Pathfinder
|
||||
class RippleLineCache
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr <RippleLineCache> pointer;
|
||||
typedef pointer const& ref;
|
||||
|
||||
explicit RippleLineCache (Ledger::ref l);
|
||||
|
||||
Ledger::ref getLedger () // VFALCO TODO const?
|
||||
{
|
||||
return mLedger;
|
||||
}
|
||||
|
||||
AccountItems& getRippleLines (const uint160& accountID);
|
||||
|
||||
private:
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
Ledger::pointer mLedger;
|
||||
|
||||
boost::unordered_map <uint160, AccountItems::pointer> mRLMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
60
src/ripple_app/paths/ripple_RippleState.cpp
Normal file
60
src/ripple_app/paths/ripple_RippleState.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
AccountItem::pointer RippleState::makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry)
|
||||
{
|
||||
if (!ledgerEntry || ledgerEntry->getType () != ltRIPPLE_STATE)
|
||||
return AccountItem::pointer ();
|
||||
|
||||
RippleState* rs = new RippleState (ledgerEntry);
|
||||
rs->setViewAccount (accountID);
|
||||
|
||||
return AccountItem::pointer (rs);
|
||||
}
|
||||
|
||||
RippleState::RippleState (SerializedLedgerEntry::ref ledgerEntry) : AccountItem (ledgerEntry),
|
||||
mValid (false),
|
||||
mViewLowest (true),
|
||||
|
||||
mLowLimit (ledgerEntry->getFieldAmount (sfLowLimit)),
|
||||
mHighLimit (ledgerEntry->getFieldAmount (sfHighLimit)),
|
||||
|
||||
mLowID (mLowLimit.getIssuer ()),
|
||||
mHighID (mHighLimit.getIssuer ()),
|
||||
|
||||
mBalance (ledgerEntry->getFieldAmount (sfBalance))
|
||||
{
|
||||
mFlags = mLedgerEntry->getFieldU32 (sfFlags);
|
||||
|
||||
mLowQualityIn = mLedgerEntry->getFieldU32 (sfLowQualityIn);
|
||||
mLowQualityOut = mLedgerEntry->getFieldU32 (sfLowQualityOut);
|
||||
|
||||
mHighQualityIn = mLedgerEntry->getFieldU32 (sfHighQualityIn);
|
||||
mHighQualityOut = mLedgerEntry->getFieldU32 (sfHighQualityOut);
|
||||
|
||||
mValid = true;
|
||||
}
|
||||
|
||||
void RippleState::setViewAccount (const uint160& accountID)
|
||||
{
|
||||
bool bViewLowestNew = mLowID == accountID;
|
||||
|
||||
if (bViewLowestNew != mViewLowest)
|
||||
{
|
||||
mViewLowest = bViewLowestNew;
|
||||
mBalance.negate ();
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value RippleState::getJson (int)
|
||||
{
|
||||
Json::Value ret (Json::objectValue);
|
||||
ret["low_id"] = mLowID.GetHex ();
|
||||
ret["high_id"] = mHighID.GetHex ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
122
src/ripple_app/paths/ripple_RippleState.h
Normal file
122
src/ripple_app/paths/ripple_RippleState.h
Normal file
@@ -0,0 +1,122 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_RIPPLESTATE_H
|
||||
#define RIPPLE_RIPPLESTATE_H
|
||||
|
||||
//
|
||||
// A ripple line's state.
|
||||
// - Isolate ledger entry format.
|
||||
//
|
||||
|
||||
class RippleState : public AccountItem
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr <RippleState> pointer;
|
||||
|
||||
public:
|
||||
RippleState () { }
|
||||
|
||||
virtual ~RippleState () { }
|
||||
|
||||
AccountItem::pointer makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry);
|
||||
|
||||
LedgerEntryType getType ()
|
||||
{
|
||||
return ltRIPPLE_STATE;
|
||||
}
|
||||
|
||||
void setViewAccount (const uint160& accountID);
|
||||
|
||||
const uint160& getAccountID () const
|
||||
{
|
||||
return mViewLowest ? mLowID : mHighID;
|
||||
}
|
||||
|
||||
const uint160& getAccountIDPeer () const
|
||||
{
|
||||
return !mViewLowest ? mLowID : mHighID;
|
||||
}
|
||||
|
||||
// True, Provided auth to peer.
|
||||
bool getAuth () const
|
||||
{
|
||||
return isSetBit (mFlags, mViewLowest ? lsfLowAuth : lsfHighAuth);
|
||||
}
|
||||
|
||||
bool getAuthPeer () const
|
||||
{
|
||||
return isSetBit (mFlags, !mViewLowest ? lsfLowAuth : lsfHighAuth);
|
||||
}
|
||||
|
||||
const STAmount& getBalance () const
|
||||
{
|
||||
return mBalance;
|
||||
}
|
||||
|
||||
const STAmount& getLimit () const
|
||||
{
|
||||
return mViewLowest ? mLowLimit : mHighLimit;
|
||||
}
|
||||
|
||||
const STAmount& getLimitPeer () const
|
||||
{
|
||||
return !mViewLowest ? mLowLimit : mHighLimit;
|
||||
}
|
||||
|
||||
uint32 getQualityIn () const
|
||||
{
|
||||
return ((uint32) (mViewLowest ? mLowQualityIn : mHighQualityIn));
|
||||
}
|
||||
|
||||
uint32 getQualityOut () const
|
||||
{
|
||||
return ((uint32) (mViewLowest ? mLowQualityOut : mHighQualityOut));
|
||||
}
|
||||
|
||||
SerializedLedgerEntry::pointer getSLE ()
|
||||
{
|
||||
return mLedgerEntry;
|
||||
}
|
||||
|
||||
const SerializedLedgerEntry& peekSLE () const
|
||||
{
|
||||
return *mLedgerEntry;
|
||||
}
|
||||
|
||||
SerializedLedgerEntry& peekSLE ()
|
||||
{
|
||||
return *mLedgerEntry;
|
||||
}
|
||||
|
||||
Json::Value getJson (int);
|
||||
|
||||
Blob getRaw () const;
|
||||
|
||||
private:
|
||||
explicit RippleState (SerializedLedgerEntry::ref ledgerEntry); // For accounts in a ledger
|
||||
|
||||
private:
|
||||
bool mValid;
|
||||
bool mViewLowest;
|
||||
|
||||
uint32 mFlags;
|
||||
|
||||
STAmount mLowLimit;
|
||||
STAmount mHighLimit;
|
||||
|
||||
uint160 mLowID;
|
||||
uint160 mHighID;
|
||||
|
||||
uint64 mLowQualityIn;
|
||||
uint64 mLowQualityOut;
|
||||
uint64 mHighQualityIn;
|
||||
uint64 mHighQualityOut;
|
||||
|
||||
STAmount mBalance;
|
||||
};
|
||||
#endif
|
||||
// vim:ts=4
|
||||
Reference in New Issue
Block a user