mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
933 lines
37 KiB
C++
933 lines
37 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
namespace ripple {
|
|
|
|
// 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 const seed = 0;
|
|
return beast::hardened_hash<aciSource>{seed}(asValue);
|
|
}
|
|
|
|
// 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 (is_bit_set (uFlags, STPathElement::typeAccount) || !!uAccountID)
|
|
jvFlags.append (!!is_bit_set (uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account");
|
|
|
|
if (is_bit_set (uFlags, STPathElement::typeCurrency) || !!uCurrencyID)
|
|
jvFlags.append (!!is_bit_set (uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency");
|
|
|
|
if (is_bit_set (uFlags, STPathElement::typeIssuer) || !!uIssuerID)
|
|
jvFlags.append (!!is_bit_set (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 = is_bit_set (iType, STPathElement::typeAccount);
|
|
// true, iff currency supplied.
|
|
// Currency is specified for the output of the current node.
|
|
const bool bCurrency = is_bit_set (iType, STPathElement::typeCurrency);
|
|
// Issuer is specified for the output of the current node.
|
|
const bool bIssuer = is_bit_set (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 = is_bit_set (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 ((is_bit_set (sleBck->getFieldU32 (sfFlags), lsfRequireAuth)
|
|
&& !is_bit_set (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))
|
|
&& sleRippleState->getFieldAmount(sfBalance) == zero) // 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 <= zero
|
|
&& -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 specifies 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 (std::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 = is_bit_set (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 = is_bit_set (pnPrv.uFlags, STPathElement::typeAccount);
|
|
const bool bNxtAccount = is_bit_set (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 ());
|
|
}
|
|
|
|
/** Check if a sequence of three accounts violates the no ripple constrains
|
|
[first] -> [second] -> [third]
|
|
Disallowed if 'second' set no ripple on [first]->[second] and [second]->[third]
|
|
*/
|
|
void PathState::checkNoRipple (
|
|
uint160 const& firstAccount,
|
|
uint160 const& secondAccount, // This is the account whose constraints we are checking
|
|
uint160 const& thirdAccount,
|
|
uint160 const& currency)
|
|
{
|
|
// fetch the ripple lines into and out of this node
|
|
SLE::pointer sleIn = lesEntries.entryCache (ltRIPPLE_STATE,
|
|
Ledger::getRippleStateIndex (firstAccount, secondAccount, currency));
|
|
SLE::pointer sleOut = lesEntries.entryCache (ltRIPPLE_STATE,
|
|
Ledger::getRippleStateIndex (secondAccount, thirdAccount, currency));
|
|
|
|
if (!sleIn || !sleOut)
|
|
{
|
|
terStatus = terNO_LINE;
|
|
}
|
|
else if (
|
|
is_bit_set (sleIn->getFieldU32 (sfFlags),
|
|
(secondAccount > firstAccount) ? lsfHighNoRipple : lsfLowNoRipple) &&
|
|
is_bit_set (sleOut->getFieldU32 (sfFlags),
|
|
(secondAccount > thirdAccount) ? lsfHighNoRipple : lsfLowNoRipple))
|
|
{
|
|
WriteLog (lsINFO, RippleCalc) << "Path violates noRipple constraint between " <<
|
|
RippleAddress::createHumanAccountID (firstAccount) << ", " <<
|
|
RippleAddress::createHumanAccountID (secondAccount) << " and " <<
|
|
RippleAddress::createHumanAccountID (thirdAccount);
|
|
|
|
terStatus = terNO_RIPPLE;
|
|
}
|
|
}
|
|
|
|
// Check a fully-expanded path to make sure it doesn't violate no-Ripple settings
|
|
void PathState::checkNoRipple (uint160 const& uDstAccountID, uint160 const& uSrcAccountID)
|
|
{
|
|
|
|
// There must be at least one node for there to be two consecutive ripple lines
|
|
if (vpnNodes.size() == 0)
|
|
return;
|
|
|
|
if (vpnNodes.size() == 1)
|
|
{
|
|
// There's just one link in the path
|
|
// We only need to check source-node-dest
|
|
if (is_bit_set (vpnNodes[0].uFlags, STPathElement::typeAccount) &&
|
|
(vpnNodes[0].uAccountID != uSrcAccountID) &&
|
|
(vpnNodes[0].uAccountID != uDstAccountID))
|
|
{
|
|
if (saInReq.getCurrency() != saOutReq.getCurrency())
|
|
terStatus = terNO_LINE;
|
|
else
|
|
checkNoRipple (uSrcAccountID, vpnNodes[0].uAccountID, uDstAccountID,
|
|
vpnNodes[0].uCurrencyID);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check source <-> first <-> second
|
|
if (is_bit_set (vpnNodes[0].uFlags, STPathElement::typeAccount) &&
|
|
is_bit_set (vpnNodes[1].uFlags, STPathElement::typeAccount) &&
|
|
(vpnNodes[0].uAccountID != uSrcAccountID))
|
|
{
|
|
if ((vpnNodes[0].uCurrencyID != vpnNodes[1].uCurrencyID))
|
|
{
|
|
terStatus = terNO_LINE;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
checkNoRipple (uSrcAccountID, vpnNodes[0].uAccountID, vpnNodes[1].uAccountID,
|
|
vpnNodes[0].uCurrencyID);
|
|
if (tesSUCCESS != terStatus)
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check second_from_last <-> last <-> destination
|
|
size_t s = vpnNodes.size() - 2;
|
|
if (is_bit_set (vpnNodes[s].uFlags, STPathElement::typeAccount) &&
|
|
is_bit_set (vpnNodes[s+1].uFlags, STPathElement::typeAccount) &&
|
|
(uDstAccountID != vpnNodes[s+1].uAccountID))
|
|
{
|
|
if ((vpnNodes[s].uCurrencyID != vpnNodes[s+1].uCurrencyID))
|
|
{
|
|
terStatus = terNO_LINE;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
checkNoRipple (vpnNodes[s].uAccountID, vpnNodes[s+1].uAccountID, uDstAccountID,
|
|
vpnNodes[s].uCurrencyID);
|
|
if (tesSUCCESS != terStatus)
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
// Loop through all nodes that have a prior node and successor nodes
|
|
// These are the nodes whose no ripple constratints could be violated
|
|
for (int i = 1; i < (vpnNodes.size() - 1); ++i)
|
|
{
|
|
|
|
if (is_bit_set (vpnNodes[i-1].uFlags, STPathElement::typeAccount) &&
|
|
is_bit_set (vpnNodes[i].uFlags, STPathElement::typeAccount) &&
|
|
is_bit_set (vpnNodes[i+1].uFlags, STPathElement::typeAccount))
|
|
{ // two consecutive account-to-account links
|
|
|
|
uint160 const& currencyID = vpnNodes[i].uCurrencyID;
|
|
if ((vpnNodes[i-1].uCurrencyID != currencyID) ||
|
|
(vpnNodes[i+1].uCurrencyID != currencyID))
|
|
{
|
|
terStatus = temBAD_PATH;
|
|
return;
|
|
}
|
|
checkNoRipple (
|
|
vpnNodes[i-1].uAccountID, vpnNodes[i].uAccountID, vpnNodes[i+1].uAccountID,
|
|
currencyID);
|
|
if (terStatus != tesSUCCESS)
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
} // ripple
|