//------------------------------------------------------------------------------ /* Copyright (c) 2011-2013, OpenCoin, Inc. */ //============================================================================== // TODO: // - Do automatic bridging via XRP. // // OPTIMIZE: When calculating path increment, note if increment consumes all liquidity. No need to revisit path in the future if // all liquidity is used. // class RippleCalc; // for logging std::size_t hash_value (const aciSource& asValue) { std::size_t seed = 0; asValue.get<0> ().hash_combine (seed); asValue.get<1> ().hash_combine (seed); asValue.get<2> ().hash_combine (seed); return seed; } // Compare the non-calculated fields. bool PathState::Node::operator== (const Node& pnOther) const { return pnOther.uFlags == uFlags && pnOther.uAccountID == uAccountID && pnOther.uCurrencyID == uCurrencyID && pnOther.uIssuerID == uIssuerID; } // This is for debugging not end users. Output names can be changed without warning. Json::Value PathState::Node::getJson () const { Json::Value jvNode (Json::objectValue); Json::Value jvFlags (Json::arrayValue); jvNode["type"] = uFlags; if (isSetBit (uFlags, STPathElement::typeAccount) || !!uAccountID) jvFlags.append (!!isSetBit (uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account"); if (isSetBit (uFlags, STPathElement::typeCurrency) || !!uCurrencyID) jvFlags.append (!!isSetBit (uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency"); if (isSetBit (uFlags, STPathElement::typeIssuer) || !!uIssuerID) jvFlags.append (!!isSetBit (uFlags, STPathElement::typeIssuer) == !!uIssuerID ? "issuer" : "-issuer"); jvNode["flags"] = jvFlags; if (!!uAccountID) jvNode["account"] = RippleAddress::createHumanAccountID (uAccountID); if (!!uCurrencyID) jvNode["currency"] = STAmount::createHumanCurrency (uCurrencyID); if (!!uIssuerID) jvNode["issuer"] = RippleAddress::createHumanAccountID (uIssuerID); if (saRevRedeem) jvNode["rev_redeem"] = saRevRedeem.getFullText (); if (saRevIssue) jvNode["rev_issue"] = saRevIssue.getFullText (); if (saRevDeliver) jvNode["rev_deliver"] = saRevDeliver.getFullText (); if (saFwdRedeem) jvNode["fwd_redeem"] = saFwdRedeem.getFullText (); if (saFwdIssue) jvNode["fwd_issue"] = saFwdIssue.getFullText (); if (saFwdDeliver) jvNode["fwd_deliver"] = saFwdDeliver.getFullText (); return jvNode; } // // PathState implementation // // Return true, iff lhs has less priority than rhs. bool PathState::lessPriority (PathState& lhs, PathState& rhs) { // First rank is quality. if (lhs.uQuality != rhs.uQuality) return lhs.uQuality > rhs.uQuality; // Bigger is worse. // Second rank is best quantity. if (lhs.saOutPass != rhs.saOutPass) return lhs.saOutPass < rhs.saOutPass; // Smaller is worse. // Third rank is path index. return lhs.mIndex > rhs.mIndex; // Bigger is worse. } // Make sure last path node delivers to uAccountID: uCurrencyID from uIssuerID. // // If the unadded next node as specified by arguments would not work as is, then add the necessary nodes so it would work. // // Rules: // - Currencies must be converted via an offer. // - A node names it's output. // - A ripple nodes output issuer must be the node's account or the next node's account. // - Offers can only go directly to another offer if the currency and issuer are an exact match. // - Real issuers must be specified for non-XRP. TER PathState::pushImply ( const uint160& uAccountID, // --> Delivering to this account. const uint160& uCurrencyID, // --> Delivering this currency. const uint160& uIssuerID) // --> Delivering this issuer. { const Node& pnPrv = vpnNodes.back (); TER terResult = tesSUCCESS; WriteLog (lsTRACE, RippleCalc) << "pushImply> " << RippleAddress::createHumanAccountID (uAccountID) << " " << STAmount::createHumanCurrency (uCurrencyID) << " " << RippleAddress::createHumanAccountID (uIssuerID); if (pnPrv.uCurrencyID != uCurrencyID) { // Currency is different, need to convert via an offer. terResult = pushNode ( // Offer. !!uCurrencyID ? STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeCurrency, ACCOUNT_XRP, // Placeholder for offers. uCurrencyID, // The offer's output is what is now wanted. uIssuerID); } const Node& pnBck = vpnNodes.back (); // For ripple, non-XRP, ensure the issuer is on at least one side of the transaction. if (tesSUCCESS == terResult && !!uCurrencyID // Not XRP. && (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs. && uAccountID != uIssuerID)) // Current is not receiving own IOUs. { // Need to ripple through uIssuerID's account. terResult = pushNode ( STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer, uIssuerID, // Intermediate account is the needed issuer. uCurrencyID, uIssuerID); } WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushImply< : %s") % transToken (terResult)); return terResult; } // Append a node and insert before it any implied nodes. // Offers may go back to back. // <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY TER PathState::pushNode ( const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) { Node pnCur; const bool bFirst = vpnNodes.empty (); const Node& pnPrv = bFirst ? Node () : vpnNodes.back (); // true, iff node is a ripple account. false, iff node is an offer node. const bool bAccount = isSetBit (iType, STPathElement::typeAccount); // true, iff currency supplied. // Currency is specified for the output of the current node. const bool bCurrency = isSetBit (iType, STPathElement::typeCurrency); // Issuer is specified for the output of the current node. const bool bIssuer = isSetBit (iType, STPathElement::typeIssuer); TER terResult = tesSUCCESS; WriteLog (lsTRACE, RippleCalc) << "pushNode> " << iType << ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-") << " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-") << "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-"); pnCur.uFlags = iType; pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; if (iType & ~STPathElement::typeValidBits) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits."; terResult = temBAD_PATH; } else if (bIssuer && !pnCur.uCurrencyID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP."; terResult = temBAD_PATH; } else if (bIssuer && !uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer."; terResult = temBAD_PATH; } else if (!bAccount && !bCurrency && !bIssuer) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer."; terResult = temBAD_PATH; } else if (bAccount) { // Account link pnCur.uAccountID = uAccountID; pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP; pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (bFirst) { // The first node is always correct as is. nothing (); } else if (!uAccountID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account."; terResult = temBAD_PATH; } else { // Add required intermediate nodes to deliver to current account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account."; terResult = pushImply ( pnCur.uAccountID, // Current account. pnCur.uCurrencyID, // Wanted currency. !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer. // Note: pnPrv may no longer be the immediately previous node. } if (tesSUCCESS == terResult && !vpnNodes.empty ()) { const Node& pnBck = vpnNodes.back (); bool bBckAccount = isSetBit (pnBck.uFlags, STPathElement::typeAccount); if (bBckAccount) { SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); if (!sleRippleState) { WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; WriteLog (lsTRACE, RippleCalc) << getJson (); terResult = terNO_LINE; } else { WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID)); bool bHigh = pnBck.uAccountID > pnCur.uAccountID; if (!sleBck) { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID); terResult = terNO_ACCOUNT; } else if ((isSetBit (sleBck->getFieldU32 (sfFlags), lsfRequireAuth) && !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth))) && sleRippleState->getFieldAmount(sfBalance).isZero()) // CHECKME { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth."; terResult = terNO_AUTH; } if (tesSUCCESS == terResult) { STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID); STAmount saLimit; if (!saOwed.isPositive () && -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID))) { WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("pushNode: dry: saOwed=%s saLimit=%s") % saOwed % saLimit); terResult = tecPATH_DRY; } } } } } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } else { // Offer link // Offers bridge a change in currency & issuer or just a change in issuer. pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? !!pnPrv.uIssuerID ? pnPrv.uIssuerID // Default to previous issuer : pnPrv.uAccountID // Or previous account if no previous issuer. : ACCOUNT_XRP; pnCur.saRateMax = saZero; pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer."; terResult = temBAD_PATH; } else if (!!pnPrv.uAccountID) { // Previous is an account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer."; // Insert intermediary issuer account if needed. terResult = pushImply ( ACCOUNT_XRP, // Rippling, but offers don't have an account. pnPrv.uCurrencyID, pnPrv.uIssuerID); } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushNode< : %s") % transToken (terResult)); return terResult; } // Set to an expanded path. // // terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, or temBAD_PATH_LOOP void PathState::setExpanded ( const LedgerEntrySet& lesSource, const STPath& spSourcePath, const uint160& uReceiverID, const uint160& uSenderID ) { uQuality = 1; // Mark path as active. const uint160 uMaxCurrencyID = saInReq.getCurrency (); const uint160 uMaxIssuerID = saInReq.getIssuer (); const uint160 uOutCurrencyID = saOutReq.getCurrency (); const uint160 uOutIssuerID = saOutReq.getIssuer (); const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_XRP; // Sender is always issuer for non-XRP. WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded> %s") % spSourcePath.getJson (0)); lesEntries = lesSource.duplicate (); terStatus = tesSUCCESS; // XRP with issuer is malformed. if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID)) terStatus = temBAD_PATH; // Push sending node. // For non-XRP, issuer is always sending account. // - Trying to expand, not-compact. // - Every issuer will be traversed through. if (tesSUCCESS == terStatus) terStatus = pushNode ( !!uMaxCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uSenderID, uMaxCurrencyID, // Max specifes the currency. uSenderIssuerID); WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: pushed: account=%s currency=%s issuer=%s") % RippleAddress::createHumanAccountID (uSenderID) % STAmount::createHumanCurrency (uMaxCurrencyID) % RippleAddress::createHumanAccountID (uSenderIssuerID)); if (tesSUCCESS == terStatus && uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender. { // May have an implied account node. // - If it was XRP, then issuers would have matched. // Figure out next node properties for implied node. const uint160 uNxtCurrencyID = spSourcePath.size () ? spSourcePath.getElement (0).getCurrency () // Use next node. : uOutCurrencyID; // Use send. const uint160 uNxtAccountID = spSourcePath.size () ? spSourcePath.getElement (0).getAccountID () : !!uOutCurrencyID ? uOutIssuerID == uReceiverID ? uReceiverID : uOutIssuerID // Use implied node. : ACCOUNT_XRP; WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: implied check: uMaxIssuerID=%s uSenderIssuerID=%s uNxtCurrencyID=%s uNxtAccountID=%s") % RippleAddress::createHumanAccountID (uMaxIssuerID) % RippleAddress::createHumanAccountID (uSenderIssuerID) % STAmount::createHumanCurrency (uNxtCurrencyID) % RippleAddress::createHumanAccountID (uNxtAccountID)); // Can't just use push implied, because it can't compensate for next account. if (!uNxtCurrencyID // Next is XRP, offer next. Must go through issuer. || uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next... || uMaxIssuerID != uNxtAccountID) // Next is not implied issuer { WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: sender implied: account=%s currency=%s issuer=%s") % RippleAddress::createHumanAccountID (uMaxIssuerID) % STAmount::createHumanCurrency (uMaxCurrencyID) % RippleAddress::createHumanAccountID (uMaxIssuerID)); // Add account implied by SendMax. terStatus = pushNode ( !!uMaxCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uMaxIssuerID, uMaxCurrencyID, uMaxIssuerID); } } BOOST_FOREACH (const STPathElement & speElement, spSourcePath) { if (tesSUCCESS == terStatus) { WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: element in path:")); terStatus = pushNode (speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ()); } } const Node& pnPrv = vpnNodes.back (); if (tesSUCCESS == terStatus && !!uOutCurrencyID // Next is not XRP && uOutIssuerID != uReceiverID // Out issuer is not receiver && (pnPrv.uCurrencyID != uOutCurrencyID // Previous will be an offer. || pnPrv.uAccountID != uOutIssuerID)) // Need the implied issuer. { // Add implied account. WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: receiver implied: account=%s currency=%s issuer=%s") % RippleAddress::createHumanAccountID (uOutIssuerID) % STAmount::createHumanCurrency (uOutCurrencyID) % RippleAddress::createHumanAccountID (uOutIssuerID)); terStatus = pushNode ( !!uOutCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uOutIssuerID, uOutCurrencyID, uOutIssuerID); } if (tesSUCCESS == terStatus) { // Create receiver node. // Last node is always an account. terStatus = pushNode ( !!uOutCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uReceiverID, // Receive to output uOutCurrencyID, // Desired currency uReceiverID); } if (tesSUCCESS == terStatus) { // Look for first mention of source in nodes and detect loops. // Note: The output is not allowed to be a source. const unsigned int uNodes = vpnNodes.size (); for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode) { const Node& pnCur = vpnNodes[uNode]; if (!umForward.insert (std::make_pair (boost::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second) { // Failed to insert. Have a loop. WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: loop detected: %s") % getJson ()); terStatus = temBAD_PATH_LOOP; } } } WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: in=%s/%s out=%s/%s %s") % STAmount::createHumanCurrency (uMaxCurrencyID) % RippleAddress::createHumanAccountID (uMaxIssuerID) % STAmount::createHumanCurrency (uOutCurrencyID) % RippleAddress::createHumanAccountID (uOutIssuerID) % getJson ()); } // Set to a canonical path. // - Remove extra elements // - Assumes path is expanded. // // We do canonicalization to: // - Prevent waste in the ledger. // - Allow longer paths to be specified than would otherwise be allowed. // // Optimization theory: // - Can omit elements that the expansion routine derives. // - Can pack some elements into other elements. // // Rules: // - SendMax if not specified, defaults currency to send and if not sending XRP defaults issuer to sender. // - All paths start with the sender account. // - Currency and issuer is from SendMax. // - All paths end with the destination account. // // Optimization: // - An XRP output implies an offer node or destination node is next. // - A change in currency implies an offer node. // - A change in issuer... void PathState::setCanonical ( const PathState& psExpanded ) { assert (false); saInAct = psExpanded.saInAct; saOutAct = psExpanded.saOutAct; const uint160 uMaxCurrencyID = saInAct.getCurrency (); const uint160 uMaxIssuerID = saInAct.getIssuer (); const uint160 uOutCurrencyID = saOutAct.getCurrency (); const uint160 uOutIssuerID = saOutAct.getIssuer (); unsigned int uNode = 0; unsigned int uEnd = psExpanded.vpnNodes.size (); // The node, indexed by 0, not to include. uint160 uDstAccountID = psExpanded.vpnNodes[uEnd].uAccountID; // FIXME: This can't be right uint160 uAccountID = psExpanded.vpnNodes[0].uAccountID; uint160 uCurrencyID = uMaxCurrencyID; uint160 uIssuerID = uMaxIssuerID; // Node 0 is a composite of the sending account and saInAct. ++uNode; // skip node 0 // Last node is implied: Always skip last node --uEnd; // skip last node // saInAct // - currency is always the same as vpnNodes[0]. #if 1 if (uNode != uEnd && uMaxIssuerID != uAccountID) { // saInAct issuer is not the sender. This forces an implied node. // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: uNode=%d uEnd=%d") % uNode % uEnd); // skip node 1 uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID; ++uNode; } #else if (uNode != uEnd) { // Have another node bool bKeep = false; if (uMaxIssuerID != uAccountID) { } if (uMaxCurrencyID) // Not sending XRP. { // Node 1 must be an account. if (uMaxIssuerID != uAccountID) { // Node 1 is required to specify issuer. bKeep = true; } else { // Node 1 must be an account } } else { // Node 1 must be an order book. bKeep = true; } if (bKeep) { uCurrencyID = psExpanded.vpnNodes[uNode].uCurrencyID; uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID; ++uNode; // Keep it. } } #endif if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID) { // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: uNode=%d uEnd=%d") % uNode % uEnd); // The next to last node is saOutAct if an issuer different from receiver is supplied. // The next to last node can be implied. --uEnd; } const Node& pnEnd = psExpanded.vpnNodes[uEnd]; if (uNode != uEnd && !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID) { // The current end node is an offer converting to saOutAct's currency and issuer and can be implied. // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: uNode=%d uEnd=%d") % uNode % uEnd); --uEnd; } // Do not include uEnd. for (; uNode != uEnd; ++uNode) { // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: uNode=%d uEnd=%d") % uNode % uEnd); const Node& pnPrv = psExpanded.vpnNodes[uNode - 1]; const Node& pnCur = psExpanded.vpnNodes[uNode]; const Node& pnNxt = psExpanded.vpnNodes[uNode + 1]; const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount); bool bSkip = false; if (bCurAccount) { // Currently at an account. // Output is non-XRP and issuer is account. if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID) { // Account issues itself. // XXX Not good enough. Previous account must mention it. bSkip = true; } } else { // Currently at an offer. const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount); const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount); if (bPrvAccount && bNxtAccount // Offer surrounded by accounts. && pnPrv.uCurrencyID != pnNxt.uCurrencyID) { // Offer can be implied by currency change. // XXX What about issuer? bSkip = true; } } if (!bSkip) { // Copy node Node pnNew; bool bSetAccount = bCurAccount; bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID; // XXX What if we need the next account because we want to skip it? bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID; pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0) | (bSetCurrency ? STPathElement::typeCurrency : 0) | (bSetIssuer ? STPathElement::typeIssuer : 0); if (bSetAccount) pnNew.uAccountID = pnCur.uAccountID; if (bSetCurrency) { pnNew.uCurrencyID = pnCur.uCurrencyID; uCurrencyID = pnNew.uCurrencyID; } if (bSetIssuer) pnNew.uIssuerID = pnCur.uIssuerID; // XXX ^^^ What about setting uIssuerID? if (bSetCurrency && !uCurrencyID) uIssuerID.zero (); vpnNodes.push_back (pnNew); } } WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setCanonical: in=%s/%s out=%s/%s %s") % STAmount::createHumanCurrency (uMaxCurrencyID) % RippleAddress::createHumanAccountID (uMaxIssuerID) % STAmount::createHumanCurrency (uOutCurrencyID) % RippleAddress::createHumanAccountID (uOutIssuerID) % getJson ()); } // This is for debugging not end users. Output names can be changed without warning. Json::Value PathState::getJson () const { Json::Value jvPathState (Json::objectValue); Json::Value jvNodes (Json::arrayValue); BOOST_FOREACH (const Node & pnNode, vpnNodes) { jvNodes.append (pnNode.getJson ()); } jvPathState["status"] = terStatus; jvPathState["index"] = mIndex; jvPathState["nodes"] = jvNodes; if (saInReq) jvPathState["in_req"] = saInReq.getJson (0); if (saInAct) jvPathState["in_act"] = saInAct.getJson (0); if (saInPass) jvPathState["in_pass"] = saInPass.getJson (0); if (saOutReq) jvPathState["out_req"] = saOutReq.getJson (0); if (saOutAct) jvPathState["out_act"] = saOutAct.getJson (0); if (saOutPass) jvPathState["out_pass"] = saOutPass.getJson (0); if (uQuality) jvPathState["uQuality"] = boost::str (boost::format ("%d") % uQuality); return jvPathState; }