// 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_STR; // Sender is always issuer for non-STR. WriteLog (lsTRACE, RippleCalc) << "setExpanded> " << spSourcePath.getJson (0); lesEntries = lesSource.duplicate (); terStatus = tesSUCCESS; // STR with issuer is malformed. if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID)) terStatus = temBAD_PATH; // Push sending node. // For non-STR, 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) << "setExpanded: pushed:" << " account=" << RippleAddress::createHumanAccountID (uSenderID) << " currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) << " issuer=" << RippleAddress::createHumanAccountID (uSenderIssuerID); if (tesSUCCESS == terStatus && uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender. { // May have an implied account node. // - If it was STR, 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_STR; WriteLog (lsDEBUG, RippleCalc) << "setExpanded: implied check:" << " uMaxIssuerID=" << RippleAddress::createHumanAccountID (uMaxIssuerID) << " uSenderIssuerID=" << RippleAddress::createHumanAccountID (uSenderIssuerID) << " uNxtCurrencyID=" << STAmount::createHumanCurrency (uNxtCurrencyID) << " uNxtAccountID=" << RippleAddress::createHumanAccountID (uNxtAccountID); // Can't just use push implied, because it can't compensate for next account. if (!uNxtCurrencyID // Next is STR, offer next. Must go through issuer. || uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next... || uMaxIssuerID != uNxtAccountID) // Next is not implied issuer { WriteLog (lsDEBUG, RippleCalc) << "setExpanded: sender implied:" << " account=" << RippleAddress::createHumanAccountID (uMaxIssuerID) << " currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) << " issuer=" << 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) << "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 STR && 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) << "setExpanded: receiver implied:" << " account=" << RippleAddress::createHumanAccountID (uOutIssuerID) << " currency=" << STAmount::createHumanCurrency (uOutCurrencyID) << " issuer=" << 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) << "setExpanded: 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 (); }
// Set this object to be an expanded path from spSourcePath - take the implied // nodes and makes them explicit. It also sanitizes the path. // // There are only two types of nodes: account nodes and order books nodes. // // You can infer some nodes automatically. If you're paying me bitstamp USD, // then there must be an intermediate bitstamp node. // // If you have accounts A and B, and they're delivery currency issued by C, then // there must be a node with account C in the middle. // // 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 TER PathState::expandPath ( STPath const& spSourcePath, AccountID const& uReceiverID, AccountID const& uSenderID) { uQuality = 1; // Mark path as active. Currency const& uMaxCurrencyID = saInReq.getCurrency (); AccountID const& uMaxIssuerID = saInReq.getIssuer (); Currency const& currencyOutID = saOutReq.getCurrency (); AccountID const& issuerOutID = saOutReq.getIssuer (); AccountID const& uSenderIssuerID = isXRP(uMaxCurrencyID) ? xrpAccount() : uSenderID; // Sender is always issuer for non-XRP. JLOG (j_.trace) << "expandPath> " << spSourcePath.getJson (0); terStatus = tesSUCCESS; // XRP with issuer is malformed. if ((isXRP (uMaxCurrencyID) && !isXRP (uMaxIssuerID)) || (isXRP (currencyOutID) && !isXRP (issuerOutID))) { JLOG (j_.debug) << "expandPath> issuer with XRP"; 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 (terStatus == tesSUCCESS) { terStatus = pushNode ( !isXRP(uMaxCurrencyID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uSenderID, uMaxCurrencyID, // Max specifies the currency. uSenderIssuerID); } JLOG (j_.debug) << "expandPath: pushed:" << " account=" << uSenderID << " currency=" << uMaxCurrencyID << " issuer=" << uSenderIssuerID; // Issuer was not same as sender. if (tesSUCCESS == terStatus && uMaxIssuerID != uSenderIssuerID) { // May have an implied account node. // - If it was XRP, then issuers would have matched. // Figure out next node properties for implied node. const auto uNxtCurrencyID = spSourcePath.size () ? Currency(spSourcePath.front ().getCurrency ()) // Use next node. : currencyOutID; // Use send. // TODO(tom): complexify this next logic further in case someone // understands it. const auto nextAccountID = spSourcePath.size () ? AccountID(spSourcePath. front ().getAccountID ()) : !isXRP(currencyOutID) ? (issuerOutID == uReceiverID) ? AccountID(uReceiverID) : AccountID(issuerOutID) // Use implied node. : xrpAccount(); JLOG (j_.debug) << "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 { JLOG (j_.debug) << "expandPath: sender implied:" << " account=" << uMaxIssuerID << " currency=" << uMaxCurrencyID << " issuer=" << uMaxIssuerID; // Add account implied by SendMax. terStatus = pushNode ( !isXRP(uMaxCurrencyID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uMaxIssuerID, uMaxCurrencyID, uMaxIssuerID); } } for (auto & speElement: spSourcePath) { if (terStatus == tesSUCCESS) { JLOG (j_.trace) << "expandPath: element in path"; terStatus = pushNode ( speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ()); } } if (terStatus == tesSUCCESS && !isXRP(currencyOutID) // Next is not XRP && issuerOutID != uReceiverID) // Out issuer is not receiver { assert (!nodes_.empty ()); auto const& backNode = nodes_.back (); if (backNode.issue_.currency != currencyOutID // Previous will be offer || backNode.account_ != issuerOutID) // Need implied issuer { // Add implied account. JLOG (j_.debug) << "expandPath: receiver implied:" << " account=" << issuerOutID << " currency=" << currencyOutID << " issuer=" << issuerOutID; terStatus = pushNode ( !isXRP(currencyOutID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, issuerOutID, currencyOutID, issuerOutID); } } if (terStatus == tesSUCCESS) { // Create receiver node. // Last node is always an account. terStatus = pushNode ( !isXRP(currencyOutID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uReceiverID, // Receive to output currencyOutID, // Desired currency uReceiverID); } if (terStatus == tesSUCCESS) { // Look for first mention of source in nodes and detect loops. // Note: The output is not allowed to be a source. unsigned int index = 0; for (auto& node: nodes_) { AccountIssue accountIssue (node.account_, node.issue_); if (!umForward.insert ({accountIssue, index++}).second) { // Failed to insert. Have a loop. JLOG (j_.debug) << "expandPath: loop detected: " << getJson (); terStatus = temBAD_PATH_LOOP; break; } } } JLOG (j_.debug) << "expandPath:" << " in=" << uMaxCurrencyID << "/" << uMaxIssuerID << " out=" << currencyOutID << "/" << issuerOutID << ": " << getJson (); return terStatus; }