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:
Tom Ritchford
2014-06-10 18:18:03 -04:00
committed by Vinnie Falco
parent a23013abc1
commit e24cba8c35
42 changed files with 1849 additions and 1310 deletions

View File

@@ -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);