mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
Better types and more comments in RippleCalc.
* Better automatic conversions to and from tagged uint160 varints. * Start using tagged variants of uint160 for Currency, Account. * Comments from 2014/6/11 RippleCalc session.
This commit is contained in:
committed by
Vinnie Falco
parent
a23013abc1
commit
e24cba8c35
@@ -25,16 +25,10 @@ namespace ripple {
|
||||
|
||||
class RippleCalc; // for logging
|
||||
|
||||
std::size_t hash_value (const AccountCurrencyIssuer& asValue)
|
||||
{
|
||||
std::size_t const seed = 0;
|
||||
return beast::hardened_hash<AccountCurrencyIssuer>{seed}(asValue);
|
||||
}
|
||||
|
||||
void PathState::clear() {
|
||||
allLiquidityConsumed_ = false;
|
||||
saInPass = zeroed (saInReq);
|
||||
saOutPass = zeroed (saOutReq);
|
||||
saInPass = saInReq.zeroed();
|
||||
saOutPass = saOutReq.zeroed();
|
||||
vUnfundedBecame.clear ();
|
||||
umReverse.clear ();
|
||||
}
|
||||
@@ -54,7 +48,7 @@ bool PathState::lessPriority (PathState& lhs, PathState& rhs)
|
||||
return lhs.mIndex > rhs.mIndex; // Bigger is worse.
|
||||
}
|
||||
|
||||
// Make sure last path node delivers to uAccountID: uCurrencyID from uIssuerID.
|
||||
// Make sure last path node delivers to account_: currency_ from issuer_.
|
||||
//
|
||||
// If the unadded next node as specified by arguments would not work as is, then
|
||||
// add the necessary nodes so it would work.
|
||||
@@ -69,56 +63,54 @@ bool PathState::lessPriority (PathState& lhs, PathState& rhs)
|
||||
// - 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.
|
||||
TER PathState::pushImpliedNodes (
|
||||
Account const& account, // --> Delivering to this account.
|
||||
Currency const& currency, // --> Delivering this currency.
|
||||
Account const& issuer) // --> Delivering this issuer.
|
||||
{
|
||||
auto const& previousNode = nodes_.back ();
|
||||
TER resultCode = tesSUCCESS;
|
||||
TER resultCode = tesSUCCESS;
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushImply>" <<
|
||||
" " << RippleAddress::createHumanAccountID (uAccountID) <<
|
||||
" " << STAmount::createHumanCurrency (uCurrencyID) <<
|
||||
" " << RippleAddress::createHumanAccountID (uIssuerID);
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushImpliedNodes>" <<
|
||||
" " << account <<
|
||||
" " << currency <<
|
||||
" " << issuer;
|
||||
|
||||
if (previousNode.uCurrencyID != uCurrencyID)
|
||||
if (nodes_.back ().currency_ != currency)
|
||||
{
|
||||
// Currency is different, need to convert via an offer.
|
||||
// Currency is different, need to convert via an offer from an order
|
||||
// book. ACCOUNT_XRP does double duty as signaling "this is an order
|
||||
// book. XRP_ACCOUNT does double duty as signaling "this is an order
|
||||
// book".
|
||||
|
||||
// Corresponds to "Implies an offer directory" in the diagram, currently
|
||||
// at https://docs.google.com/a/ripple.com/document/d/1b1RC8pKIgVZqUmjf9MW4IYxvzU7cBla4-pCSBbV4u8Q/edit
|
||||
// at http://goo.gl/Uj3HAB.
|
||||
|
||||
resultCode = 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);
|
||||
auto type = isXRP(currency) ? STPathElement::typeCurrency
|
||||
: STPathElement::typeCurrency | STPathElement::typeIssuer;
|
||||
|
||||
// The offer's output is what is now wanted.
|
||||
// XRP_ACCOUNT is a placeholder for offers.
|
||||
resultCode = pushNode (type, XRP_ACCOUNT, currency, issuer);
|
||||
}
|
||||
|
||||
auto const& pnBck = nodes_.back ();
|
||||
|
||||
// For ripple, non-XRP, ensure the issuer is on at least one side of the transaction.
|
||||
// For ripple, non-XRP, ensure the issuer is on at least one side of the
|
||||
// transaction.
|
||||
if (resultCode == tesSUCCESS
|
||||
&& !!uCurrencyID // Not XRP.
|
||||
&& (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs.
|
||||
&& uAccountID != uIssuerID)) // Current is not receiving own IOUs.
|
||||
&& !isXRP(currency)
|
||||
&& nodes_.back ().account_ != issuer
|
||||
// Previous is not issuing own IOUs.
|
||||
&& account != issuer)
|
||||
// Current is not receiving own IOUs.
|
||||
{
|
||||
// Need to ripple through uIssuerID's account.
|
||||
// Case "Implies an another node: (pushImply)" in the document.
|
||||
resultCode = pushNode (
|
||||
STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer,
|
||||
uIssuerID, // Intermediate account is the needed issuer.
|
||||
uCurrencyID,
|
||||
uIssuerID);
|
||||
// Need to ripple through issuer's account.
|
||||
// Case "Implies an another node: (pushImpliedNodes)" in the document.
|
||||
// Intermediate account is the needed issuer.
|
||||
resultCode = pushNode (
|
||||
STPathElement::typeAll, issuer, currency, issuer);
|
||||
}
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushImply< : " << transToken (resultCode);
|
||||
WriteLog (lsTRACE, RippleCalc)
|
||||
<< "pushImpliedNodes< : " << transToken (resultCode);
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
@@ -128,124 +120,129 @@ TER PathState::pushImply (
|
||||
//
|
||||
// For each non-matching pair of IssuedCurrency, there's an order book.
|
||||
//
|
||||
// <-- resultCode: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
|
||||
// <-- resultCode: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH,
|
||||
// terNO_LINE, tecPATH_DRY
|
||||
TER PathState::pushNode (
|
||||
const int iType,
|
||||
const uint160& uAccountID, // If not specified, means an order book.
|
||||
const uint160& uCurrencyID, // If not specified, default to previous.
|
||||
const uint160& uIssuerID) // If not specified, default to previous.
|
||||
Account const& account, // If not specified, means an order book.
|
||||
Currency const& currency, // If not specified, default to previous.
|
||||
Account const& issuer) // If not specified, default to previous.
|
||||
{
|
||||
path::Node node;
|
||||
const bool bFirst = nodes_.empty ();
|
||||
auto const& previousNode = bFirst ? path::Node () : nodes_.back ();
|
||||
const bool pathIsEmpty = nodes_.empty ();
|
||||
|
||||
// TODO(tom): if pathIsEmpty, we probably don't need to do ANYTHING below.
|
||||
// Indeed, we might just not even call pushNode in the first place!
|
||||
|
||||
auto const& previousNode = pathIsEmpty ? path::Node () : nodes_.back ();
|
||||
|
||||
// true, iff node is a ripple account. false, iff node is an offer node.
|
||||
const bool bAccount (iType & STPathElement::typeAccount);
|
||||
const bool hasAccount = (iType & STPathElement::typeAccount);
|
||||
|
||||
// Is currency specified for the output of the current node?
|
||||
const bool bCurrency (iType & STPathElement::typeCurrency);
|
||||
const bool hasCurrency = (iType & STPathElement::typeCurrency);
|
||||
|
||||
// Issuer is specified for the output of the current node.
|
||||
const bool bIssuer (iType & STPathElement::typeIssuer);
|
||||
const bool hasIssuer = (iType & STPathElement::typeIssuer);
|
||||
|
||||
TER resultCode = tesSUCCESS;
|
||||
TER resultCode = tesSUCCESS;
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode> " <<
|
||||
iType <<
|
||||
": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-") <<
|
||||
" " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-") <<
|
||||
"/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-");
|
||||
WriteLog (lsTRACE, RippleCalc)
|
||||
<< "pushNode> " << iType << ": "
|
||||
<< (hasAccount ? to_string(account) : std::string("-")) << " "
|
||||
<< (hasCurrency ? to_string(currency) : std::string("-")) << "/"
|
||||
<< (hasIssuer ? to_string(issuer) : std::string("-")) << "/";
|
||||
|
||||
node.uFlags = iType;
|
||||
node.uCurrencyID = bCurrency ? uCurrencyID : previousNode.uCurrencyID;
|
||||
node.currency_ = hasCurrency ? currency : Currency(previousNode.currency_);
|
||||
|
||||
if (iType & ~STPathElement::typeValidBits)
|
||||
// TODO(tom): we can probably just return immediately whenever we hit an
|
||||
// error in these next pages.
|
||||
|
||||
if (iType & ~STPathElement::typeAll)
|
||||
{
|
||||
// Of course, this could never happen.
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits.";
|
||||
|
||||
resultCode = temBAD_PATH;
|
||||
}
|
||||
else if (bIssuer && !node.uCurrencyID)
|
||||
else if (hasIssuer && !node.currency_)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP.";
|
||||
|
||||
resultCode = temBAD_PATH;
|
||||
}
|
||||
else if (bIssuer && !uIssuerID)
|
||||
else if (hasIssuer && !issuer)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer.";
|
||||
|
||||
resultCode = temBAD_PATH;
|
||||
}
|
||||
else if (!bAccount && !bCurrency && !bIssuer)
|
||||
else if (!hasAccount && !hasCurrency && !hasIssuer)
|
||||
{
|
||||
// You can't default everything to the previous node as you would make
|
||||
// no progress.
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer.";
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "pushNode: offer must specify at least currency or issuer.";
|
||||
resultCode = temBAD_PATH;
|
||||
}
|
||||
else if (bAccount)
|
||||
else if (hasAccount)
|
||||
{
|
||||
// Account link
|
||||
|
||||
node.uAccountID = uAccountID;
|
||||
node.uIssuerID = bIssuer
|
||||
? uIssuerID
|
||||
: !!node.uCurrencyID // Not XRP.
|
||||
? uAccountID
|
||||
: ACCOUNT_XRP;
|
||||
node.account_ = account;
|
||||
node.issuer_ = hasIssuer
|
||||
? issuer
|
||||
: !!node.currency_ // Not XRP.
|
||||
? account
|
||||
: XRP_ACCOUNT;
|
||||
// Zero value - for accounts.
|
||||
node.saRevRedeem = STAmount (node.uCurrencyID, uAccountID);
|
||||
node.saRevRedeem = STAmount (node.currency_, account);
|
||||
node.saRevIssue = node.saRevRedeem;
|
||||
|
||||
// For order books only - zero currency with the issuer ID.
|
||||
node.saRevDeliver = STAmount (node.uCurrencyID, node.uIssuerID);
|
||||
node.saRevDeliver = STAmount (node.currency_, node.issuer_);
|
||||
node.saFwdDeliver = node.saRevDeliver;
|
||||
|
||||
if (bFirst)
|
||||
if (pathIsEmpty)
|
||||
{
|
||||
// The first node is always correct as is.
|
||||
}
|
||||
else if (!uAccountID)
|
||||
else if (!account)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account.";
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "pushNode: specified bad account.";
|
||||
resultCode = temBAD_PATH;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add required intermediate nodes to deliver to current account.
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account.";
|
||||
WriteLog (lsTRACE, RippleCalc)
|
||||
<< "pushNode: imply for account.";
|
||||
|
||||
resultCode = pushImply (
|
||||
node.uAccountID, // Current account.
|
||||
node.uCurrencyID, // Wanted currency.
|
||||
!!node.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer.
|
||||
resultCode = pushImpliedNodes (
|
||||
node.account_, node.currency_,
|
||||
isXRP(node.currency_) ? XRP_ACCOUNT : account);
|
||||
|
||||
// Note: previousNode may no longer be the immediately previous node.
|
||||
}
|
||||
|
||||
if (resultCode == tesSUCCESS && !nodes_.empty ())
|
||||
{
|
||||
auto const& pnBck = nodes_.back ();
|
||||
bool bBckAccount = pnBck.isAccount();
|
||||
|
||||
if (bBckAccount)
|
||||
auto const& backNode = nodes_.back ();
|
||||
if (backNode.isAccount())
|
||||
{
|
||||
SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, node.uAccountID, pnBck.uCurrencyID));
|
||||
auto sleRippleState = lesEntries.entryCache (
|
||||
ltRIPPLE_STATE,
|
||||
Ledger::getRippleStateIndex (
|
||||
backNode.account_, node.account_, backNode.currency_));
|
||||
|
||||
// A "RippleState" means a balance betweeen two accounts for a
|
||||
// specific currency.
|
||||
if (!sleRippleState)
|
||||
{
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between "
|
||||
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
|
||||
<< " and "
|
||||
<< RippleAddress::createHumanAccountID (node.uAccountID)
|
||||
<< " for "
|
||||
<< STAmount::createHumanCurrency (node.uCurrencyID)
|
||||
<< "." ;
|
||||
WriteLog (lsTRACE, RippleCalc)
|
||||
<< "pushNode: No credit line between "
|
||||
<< backNode.account_ << " and " << node.account_
|
||||
<< " for " << node.currency_ << "." ;
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << getJson ();
|
||||
|
||||
@@ -253,47 +250,56 @@ TER PathState::pushNode (
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between "
|
||||
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
|
||||
<< " and "
|
||||
<< RippleAddress::createHumanAccountID (node.uAccountID)
|
||||
<< " for "
|
||||
<< STAmount::createHumanCurrency (node.uCurrencyID)
|
||||
<< "." ;
|
||||
WriteLog (lsTRACE, RippleCalc)
|
||||
<< "pushNode: Credit line found between "
|
||||
<< backNode.account_ << " and " << node.account_
|
||||
<< " for " << node.currency_ << "." ;
|
||||
|
||||
SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID));
|
||||
auto sleBck = lesEntries.entryCache (
|
||||
ltACCOUNT_ROOT,
|
||||
Ledger::getAccountRootIndex (backNode.account_));
|
||||
// Is the source account the highest numbered account ID?
|
||||
bool bHigh = pnBck.uAccountID > node.uAccountID;
|
||||
bool bHigh = backNode.account_ > node.account_;
|
||||
|
||||
if (!sleBck)
|
||||
{
|
||||
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID);
|
||||
WriteLog (lsWARNING, RippleCalc)
|
||||
<< "pushNode: delay: can't receive IOUs from "
|
||||
<< "non-existent issuer: " << backNode.account_;
|
||||
|
||||
resultCode = terNO_ACCOUNT;
|
||||
}
|
||||
else if (sleBck->getFieldU32 (sfFlags) & lsfRequireAuth
|
||||
&& !(sleRippleState->getFieldU32 (sfFlags) & (bHigh ? lsfHighAuth : lsfLowAuth))
|
||||
&& sleRippleState->getFieldAmount(sfBalance) == zero) // CHECKME
|
||||
else if ((sleBck->getFieldU32 (sfFlags) & lsfRequireAuth) &&
|
||||
!(sleRippleState->getFieldU32 (sfFlags) &
|
||||
(bHigh ? lsfHighAuth : lsfLowAuth)) &&
|
||||
sleRippleState->getFieldAmount(sfBalance) == zero)
|
||||
{
|
||||
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth.";
|
||||
WriteLog (lsWARNING, RippleCalc)
|
||||
<< "pushNode: delay: can't receive IOUs from "
|
||||
<< "issuer without auth.";
|
||||
|
||||
resultCode = terNO_AUTH;
|
||||
}
|
||||
|
||||
if (resultCode == tesSUCCESS)
|
||||
{
|
||||
STAmount saOwed = lesEntries.rippleOwed (node.uAccountID, pnBck.uAccountID, node.uCurrencyID);
|
||||
STAmount saLimit;
|
||||
STAmount saOwed = lesEntries.rippleOwed (
|
||||
node.account_, backNode.account_, node.currency_);
|
||||
STAmount saLimit;
|
||||
|
||||
if (saOwed <= zero
|
||||
&& -saOwed >= (saLimit = lesEntries.rippleLimit (node.uAccountID, pnBck.uAccountID, node.uCurrencyID)))
|
||||
{
|
||||
WriteLog (lsWARNING, RippleCalc) <<
|
||||
"pushNode: dry:" <<
|
||||
" saOwed=" << saOwed <<
|
||||
" saLimit=" << saLimit;
|
||||
if (saOwed <= zero) {
|
||||
saLimit = lesEntries.rippleLimit (
|
||||
node.account_, backNode.account_,
|
||||
node.currency_);
|
||||
if (-saOwed >= saLimit)
|
||||
{
|
||||
WriteLog (lsWARNING, RippleCalc) <<
|
||||
"pushNode: dry:" <<
|
||||
" saOwed=" << saOwed <<
|
||||
" saLimit=" << saLimit;
|
||||
|
||||
resultCode = tecPATH_DRY;
|
||||
resultCode = tecPATH_DRY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,34 +307,35 @@ TER PathState::pushNode (
|
||||
}
|
||||
|
||||
if (resultCode == tesSUCCESS)
|
||||
{
|
||||
nodes_.push_back (node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Offer link
|
||||
// Offers bridge a change in currency & issuer or just a change in issuer.
|
||||
node.uIssuerID = bIssuer
|
||||
? uIssuerID
|
||||
: !!node.uCurrencyID
|
||||
? !!previousNode.uIssuerID
|
||||
? previousNode.uIssuerID // Default to previous issuer
|
||||
: previousNode.uAccountID // Or previous account if no previous issuer.
|
||||
: ACCOUNT_XRP;
|
||||
node.saRateMax = saZero;
|
||||
node.saRevDeliver = STAmount (node.uCurrencyID, node.uIssuerID);
|
||||
node.saFwdDeliver = node.saRevDeliver;
|
||||
// Offer link.
|
||||
//
|
||||
// Offers bridge a change in currency and issuer, or just a change in
|
||||
// issuer.
|
||||
node.issuer_ = hasIssuer
|
||||
? issuer
|
||||
: !!node.currency_
|
||||
? !!previousNode.issuer_
|
||||
? Account(previousNode.issuer_) // Default to previous issuer
|
||||
: Account(previousNode.account_)
|
||||
// Or previous account if no previous issuer.
|
||||
: XRP_ACCOUNT;
|
||||
node.saRateMax = saZero;
|
||||
node.saRevDeliver = STAmount (node.currency_, node.issuer_);
|
||||
node.saFwdDeliver = node.saRevDeliver;
|
||||
|
||||
if (node.uCurrencyID.isZero() != node.uIssuerID.isZero())
|
||||
if (node.currency_.isZero() != node.issuer_.isZero())
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "pushNode: currency is inconsistent with issuer.";
|
||||
|
||||
resultCode = temBAD_PATH;
|
||||
}
|
||||
else if (previousNode.uCurrencyID == node.uCurrencyID &&
|
||||
previousNode.uIssuerID == node.uIssuerID)
|
||||
else if (previousNode.currency_ == node.currency_ &&
|
||||
previousNode.issuer_ == node.issuer_)
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) <<
|
||||
"pushNode: bad path: offer to same currency and issuer";
|
||||
@@ -337,10 +344,10 @@ TER PathState::pushNode (
|
||||
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer.";
|
||||
|
||||
// Insert intermediary issuer account if needed.
|
||||
resultCode = pushImply (
|
||||
ACCOUNT_XRP, // Rippling, but offers don't have an account.
|
||||
previousNode.uCurrencyID,
|
||||
previousNode.uIssuerID);
|
||||
resultCode = pushImpliedNodes (
|
||||
XRP_ACCOUNT, // Rippling, but offers don't have an account.
|
||||
previousNode.currency_,
|
||||
previousNode.issuer_);
|
||||
}
|
||||
|
||||
if (resultCode == tesSUCCESS)
|
||||
@@ -366,89 +373,112 @@ TER PathState::pushNode (
|
||||
//
|
||||
// If you're paying USD and getting bitcoins, there has to be an order book in
|
||||
// between.
|
||||
// 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
|
||||
)
|
||||
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH,
|
||||
// or temBAD_PATH_LOOP
|
||||
void PathState::expandPath (
|
||||
const LedgerEntrySet& lesSource,
|
||||
const STPath& spSourcePath,
|
||||
Account const& uReceiverID,
|
||||
Account const& uSenderID)
|
||||
{
|
||||
uQuality = 1; // Mark path as active.
|
||||
uQuality = 1; // Mark path as active.
|
||||
|
||||
const uint160 uMaxCurrencyID = saInReq.getCurrency ();
|
||||
const uint160 uMaxIssuerID = saInReq.getIssuer ();
|
||||
const Currency uMaxCurrencyID = saInReq.getCurrency ();
|
||||
const Account 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.
|
||||
const Currency uOutCurrencyID = saOutReq.getCurrency ();
|
||||
const Account uOutIssuerID = saOutReq.getIssuer ();
|
||||
const Account uSenderIssuerID
|
||||
= isXRP(uMaxCurrencyID) ? XRP_ACCOUNT : uSenderID;
|
||||
// Sender is always issuer for non-XRP.
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc) << "setExpanded> " << spSourcePath.getJson (0);
|
||||
WriteLog (lsTRACE, RippleCalc)
|
||||
<< "expandPath> " << spSourcePath.getJson (0);
|
||||
|
||||
lesEntries = lesSource.duplicate ();
|
||||
lesEntries = lesSource.duplicate ();
|
||||
|
||||
terStatus = tesSUCCESS;
|
||||
terStatus = tesSUCCESS;
|
||||
|
||||
// XRP with issuer is malformed.
|
||||
if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID))
|
||||
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)
|
||||
if (terStatus == tesSUCCESS)
|
||||
{
|
||||
terStatus = pushNode (
|
||||
!!uMaxCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uSenderID,
|
||||
uMaxCurrencyID, // Max specifies the currency.
|
||||
uSenderIssuerID);
|
||||
!isXRP(uMaxCurrencyID)
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency |
|
||||
STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uSenderID,
|
||||
uMaxCurrencyID, // Max specifies the currency.
|
||||
uSenderIssuerID);
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc) << "setExpanded: pushed:" <<
|
||||
" account=" << RippleAddress::createHumanAccountID (uSenderID) <<
|
||||
" currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) <<
|
||||
" issuer=" << RippleAddress::createHumanAccountID (uSenderIssuerID);
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "expandPath: pushed:"
|
||||
<< " account=" << uSenderID
|
||||
<< " currency=" << uMaxCurrencyID
|
||||
<< " issuer=" << uSenderIssuerID;
|
||||
|
||||
if (tesSUCCESS == terStatus
|
||||
&& uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender.
|
||||
&& 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 nextAccountID = spSourcePath.size ()
|
||||
? spSourcePath.getElement (0).getAccountID ()
|
||||
: !!uOutCurrencyID
|
||||
? uOutIssuerID == uReceiverID
|
||||
? uReceiverID
|
||||
: uOutIssuerID // Use implied node.
|
||||
: ACCOUNT_XRP;
|
||||
const auto uNxtCurrencyID = spSourcePath.size ()
|
||||
? Currency(spSourcePath.getElement (0).getCurrency ())
|
||||
// Use next node.
|
||||
: uOutCurrencyID;
|
||||
// Use send.
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc) << "setExpanded: implied check:" <<
|
||||
" uMaxIssuerID=" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
|
||||
" uSenderIssuerID=" << RippleAddress::createHumanAccountID (uSenderIssuerID) <<
|
||||
" uNxtCurrencyID=" << STAmount::createHumanCurrency (uNxtCurrencyID) <<
|
||||
" nextAccountID=" << RippleAddress::createHumanAccountID (nextAccountID);
|
||||
// TODO(tom): complexify this next logic further in case someone
|
||||
// understands it.
|
||||
const auto nextAccountID = spSourcePath.size ()
|
||||
? Account(spSourcePath.getElement (0).getAccountID ())
|
||||
: !isXRP(uOutCurrencyID)
|
||||
? (uOutIssuerID == uReceiverID)
|
||||
? Account(uReceiverID)
|
||||
: Account(uOutIssuerID) // Use implied node.
|
||||
: XRP_ACCOUNT;
|
||||
|
||||
// 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 != nextAccountID) // Next is not implied issuer
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "expandPath: implied check:"
|
||||
<< " uMaxIssuerID=" << uMaxIssuerID
|
||||
<< " uSenderIssuerID=" << uSenderIssuerID
|
||||
<< " uNxtCurrencyID=" << uNxtCurrencyID
|
||||
<< " nextAccountID=" << nextAccountID;
|
||||
|
||||
// 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 != nextAccountID)
|
||||
// Next is not implied issuer
|
||||
{
|
||||
WriteLog (lsDEBUG, RippleCalc) << "setExpanded: sender implied:" <<
|
||||
" account=" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
|
||||
" currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) <<
|
||||
" issuer=" << RippleAddress::createHumanAccountID (uMaxIssuerID);
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "expandPath: sender implied:"
|
||||
<< " account=" << uMaxIssuerID
|
||||
<< " currency=" << uMaxCurrencyID
|
||||
<< " issuer=" << uMaxIssuerID;
|
||||
|
||||
// Add account implied by SendMax.
|
||||
terStatus = pushNode (
|
||||
!!uMaxCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
terStatus = pushNode (
|
||||
!isXRP(uMaxCurrencyID)
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency |
|
||||
STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uMaxIssuerID,
|
||||
uMaxCurrencyID,
|
||||
@@ -456,95 +486,104 @@ void PathState::setExpanded (
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FOREACH (const STPathElement & speElement, spSourcePath)
|
||||
for (auto & speElement: spSourcePath)
|
||||
{
|
||||
if (tesSUCCESS == terStatus)
|
||||
if (terStatus == tesSUCCESS)
|
||||
{
|
||||
WriteLog (lsTRACE, RippleCalc) << "setExpanded: element in path";
|
||||
terStatus = pushNode (
|
||||
WriteLog (lsTRACE, RippleCalc) << "expandPath: element in path";
|
||||
terStatus = pushNode (
|
||||
speElement.getNodeType (), speElement.getAccountID (),
|
||||
speElement.getCurrency (), speElement.getIssuerID ());
|
||||
}
|
||||
}
|
||||
|
||||
auto const& previousNode = nodes_.back ();
|
||||
auto const& previousNode = nodes_.back ();
|
||||
|
||||
if (tesSUCCESS == terStatus
|
||||
&& !!uOutCurrencyID // Next is not XRP
|
||||
&& uOutIssuerID != uReceiverID // Out issuer is not receiver
|
||||
&& (previousNode.uCurrencyID != uOutCurrencyID // Previous will be an offer.
|
||||
|| previousNode.uAccountID != uOutIssuerID)) // Need the implied issuer.
|
||||
if (terStatus == tesSUCCESS
|
||||
&& !isXRP(uOutCurrencyID) // Next is not XRP
|
||||
&& uOutIssuerID != uReceiverID // Out issuer is not receiver
|
||||
&& (previousNode.currency_ != uOutCurrencyID
|
||||
// Previous will be an offer.
|
||||
|| previousNode.account_ != uOutIssuerID))
|
||||
// Need the implied issuer.
|
||||
{
|
||||
// Add implied account.
|
||||
WriteLog (lsDEBUG, RippleCalc) << "setExpanded: receiver implied:" <<
|
||||
" account=" << RippleAddress::createHumanAccountID (uOutIssuerID) <<
|
||||
" currency=" << STAmount::createHumanCurrency (uOutCurrencyID) <<
|
||||
" issuer=" << RippleAddress::createHumanAccountID (uOutIssuerID);
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "expandPath: receiver implied:"
|
||||
<< " account=" << uOutIssuerID
|
||||
<< " currency=" << uOutCurrencyID
|
||||
<< " issuer=" << uOutIssuerID;
|
||||
|
||||
terStatus = pushNode (
|
||||
!!uOutCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
!isXRP(uOutCurrencyID)
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency |
|
||||
STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uOutIssuerID,
|
||||
uOutCurrencyID,
|
||||
uOutIssuerID);
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terStatus)
|
||||
if (terStatus == tesSUCCESS)
|
||||
{
|
||||
// Create receiver node.
|
||||
// Last node is always an account.
|
||||
|
||||
terStatus = pushNode (
|
||||
!!uOutCurrencyID
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
!isXRP(uOutCurrencyID)
|
||||
? STPathElement::typeAccount | STPathElement::typeCurrency |
|
||||
STPathElement::typeIssuer
|
||||
: STPathElement::typeAccount | STPathElement::typeCurrency,
|
||||
uReceiverID, // Receive to output
|
||||
uOutCurrencyID, // Desired currency
|
||||
uReceiverID);
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terStatus)
|
||||
if (terStatus == tesSUCCESS)
|
||||
{
|
||||
// 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 = nodes_.size ();
|
||||
|
||||
for (unsigned int nodeIndex = 0; tesSUCCESS == terStatus && nodeIndex != uNodes; ++nodeIndex)
|
||||
for (unsigned int nodeIndex = 0;
|
||||
tesSUCCESS == terStatus && nodeIndex != uNodes; ++nodeIndex)
|
||||
{
|
||||
const auto& node = nodes_[nodeIndex];
|
||||
|
||||
AccountCurrencyIssuer aci(
|
||||
node.uAccountID, node.uCurrencyID, node.uIssuerID);
|
||||
node.account_, node.currency_, node.issuer_);
|
||||
if (!umForward.insert (std::make_pair (aci, nodeIndex)).second)
|
||||
{
|
||||
// Failed to insert. Have a loop.
|
||||
WriteLog (lsDEBUG, RippleCalc) <<
|
||||
"setExpanded: loop detected: " << getJson ();
|
||||
"expandPath: loop detected: " << getJson ();
|
||||
|
||||
terStatus = temBAD_PATH_LOOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, RippleCalc) << "setExpanded:" <<
|
||||
" in=" << STAmount::createHumanCurrency (uMaxCurrencyID) <<
|
||||
"/" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
|
||||
" out=" << STAmount::createHumanCurrency (uOutCurrencyID) <<
|
||||
"/" << RippleAddress::createHumanAccountID (uOutIssuerID) <<
|
||||
": " << getJson ();
|
||||
WriteLog (lsDEBUG, RippleCalc)
|
||||
<< "expandPath:"
|
||||
<< " in=" << uMaxCurrencyID
|
||||
<< "/" << uMaxIssuerID
|
||||
<< " out=" << uOutCurrencyID
|
||||
<< "/" << 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]
|
||||
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)
|
||||
Account const& firstAccount,
|
||||
Account const& secondAccount,
|
||||
// This is the account whose constraints we are checking
|
||||
Account const& thirdAccount,
|
||||
Currency const& currency)
|
||||
{
|
||||
// fetch the ripple lines into and out of this node
|
||||
SLE::pointer sleIn = lesEntries.entryCache (ltRIPPLE_STATE,
|
||||
@@ -562,19 +601,23 @@ void PathState::checkNoRipple (
|
||||
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);
|
||||
WriteLog (lsINFO, RippleCalc)
|
||||
<< "Path violates noRipple constraint between "
|
||||
<< firstAccount << ", "
|
||||
<< secondAccount << " and "
|
||||
<< 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)
|
||||
// Check a fully-expanded path to make sure it doesn't violate no-Ripple
|
||||
// settings.
|
||||
void PathState::checkNoRipple (
|
||||
Account const& uDstAccountID, Account const& uSrcAccountID)
|
||||
{
|
||||
// There must be at least one node for there to be two consecutive ripple lines
|
||||
// There must be at least one node for there to be two consecutive ripple
|
||||
// lines.
|
||||
if (nodes_.size() == 0)
|
||||
return;
|
||||
|
||||
@@ -583,14 +626,14 @@ void PathState::checkNoRipple (uint160 const& uDstAccountID, uint160 const& uSrc
|
||||
// There's just one link in the path
|
||||
// We only need to check source-node-dest
|
||||
if (nodes_[0].isAccount() &&
|
||||
(nodes_[0].uAccountID != uSrcAccountID) &&
|
||||
(nodes_[0].uAccountID != uDstAccountID))
|
||||
(nodes_[0].account_ != uSrcAccountID) &&
|
||||
(nodes_[0].account_ != uDstAccountID))
|
||||
{
|
||||
if (saInReq.getCurrency() != saOutReq.getCurrency())
|
||||
terStatus = terNO_LINE;
|
||||
else
|
||||
checkNoRipple (uSrcAccountID, nodes_[0].uAccountID, uDstAccountID,
|
||||
nodes_[0].uCurrencyID);
|
||||
checkNoRipple (uSrcAccountID, nodes_[0].account_, uDstAccountID,
|
||||
nodes_[0].currency_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -598,17 +641,17 @@ void PathState::checkNoRipple (uint160 const& uDstAccountID, uint160 const& uSrc
|
||||
// Check source <-> first <-> second
|
||||
if (nodes_[0].isAccount() &&
|
||||
nodes_[1].isAccount() &&
|
||||
(nodes_[0].uAccountID != uSrcAccountID))
|
||||
(nodes_[0].account_ != uSrcAccountID))
|
||||
{
|
||||
if ((nodes_[0].uCurrencyID != nodes_[1].uCurrencyID))
|
||||
if ((nodes_[0].currency_ != nodes_[1].currency_))
|
||||
{
|
||||
terStatus = terNO_LINE;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkNoRipple (uSrcAccountID, nodes_[0].uAccountID, nodes_[1].uAccountID,
|
||||
nodes_[0].uCurrencyID);
|
||||
checkNoRipple (uSrcAccountID, nodes_[0].account_, nodes_[1].account_,
|
||||
nodes_[0].currency_);
|
||||
if (tesSUCCESS != terStatus)
|
||||
return;
|
||||
}
|
||||
@@ -618,17 +661,17 @@ void PathState::checkNoRipple (uint160 const& uDstAccountID, uint160 const& uSrc
|
||||
size_t s = nodes_.size() - 2;
|
||||
if (nodes_[s].isAccount() &&
|
||||
nodes_[s + 1].isAccount() &&
|
||||
(uDstAccountID != nodes_[s+1].uAccountID))
|
||||
(uDstAccountID != nodes_[s+1].account_))
|
||||
{
|
||||
if ((nodes_[s].uCurrencyID != nodes_[s+1].uCurrencyID))
|
||||
if ((nodes_[s].currency_ != nodes_[s+1].currency_))
|
||||
{
|
||||
terStatus = terNO_LINE;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkNoRipple (nodes_[s].uAccountID, nodes_[s+1].uAccountID, uDstAccountID,
|
||||
nodes_[s].uCurrencyID);
|
||||
checkNoRipple (nodes_[s].account_, nodes_[s+1].account_,
|
||||
uDstAccountID, nodes_[s].currency_);
|
||||
if (tesSUCCESS != terStatus)
|
||||
return;
|
||||
}
|
||||
@@ -643,16 +686,16 @@ void PathState::checkNoRipple (uint160 const& uDstAccountID, uint160 const& uSrc
|
||||
nodes_[i + 1].isAccount())
|
||||
{ // Two consecutive account-to-account links
|
||||
|
||||
uint160 const& currencyID = nodes_[i].uCurrencyID;
|
||||
if ((nodes_[i-1].uCurrencyID != currencyID) ||
|
||||
(nodes_[i+1].uCurrencyID != currencyID))
|
||||
uint160 const& currencyID = nodes_[i].currency_;
|
||||
if ((nodes_[i-1].currency_ != currencyID) ||
|
||||
(nodes_[i+1].currency_ != currencyID))
|
||||
{
|
||||
terStatus = temBAD_PATH;
|
||||
return;
|
||||
}
|
||||
checkNoRipple (
|
||||
nodes_[i-1].uAccountID, nodes_[i].uAccountID, nodes_[i+1].uAccountID,
|
||||
currencyID);
|
||||
nodes_[i-1].account_, nodes_[i].account_, nodes_[i+1].account_,
|
||||
currencyID);
|
||||
if (terStatus != tesSUCCESS)
|
||||
return;
|
||||
}
|
||||
@@ -660,7 +703,8 @@ void PathState::checkNoRipple (uint160 const& uDstAccountID, uint160 const& uSrc
|
||||
}
|
||||
}
|
||||
|
||||
// This is for debugging not end users. Output names can be changed without warning.
|
||||
// This is for debugging not end users. Output names can be changed without
|
||||
// warning.
|
||||
Json::Value PathState::getJson () const
|
||||
{
|
||||
Json::Value jvPathState (Json::objectValue);
|
||||
|
||||
Reference in New Issue
Block a user