bool BookDirIterator::nextDirectory (LedgerEntrySet& les) { WriteLog (lsTRACE, Ledger) << "BookDirectoryIterator:: nextDirectory"; // Are we already at the end? if (mIndex.isZero ()) return false; // Get the ledger index of the next directory mIndex = les.getNextLedgerIndex (mIndex, mEnd); if (mIndex.isZero ()) { // We ran off the end of the book WriteLog (lsTRACE, Ledger) << "BookDirectoryIterator:: no next ledger index"; return false; } assert (mIndex < mEnd); WriteLog (lsTRACE, Ledger) << "BookDirectoryIterator:: index " << to_string (mIndex); // Retrieve the SLE from the LES mOfferDir = les.entryCache (ltDIR_NODE, mIndex); assert (mOfferDir); return bool(mOfferDir); }
static std::uint32_t rippleQuality ( LedgerEntrySet& ledger, AccountID const& destination, AccountID const& source, Currency const& currency, SField const& sfLow, SField const& sfHigh) { std::uint32_t uQuality (QUALITY_ONE); if (destination != source) { SLE::pointer sleRippleState (ledger.entryCache (ltRIPPLE_STATE, getRippleStateIndex (destination, source, currency))); // we should be able to assert(sleRippleState) here if (sleRippleState) { auto const& sfField = destination < source ? sfLow : sfHigh; uQuality = sleRippleState->isFieldPresent (sfField) ? sleRippleState->getFieldU32 (sfField) : QUALITY_ONE; if (!uQuality) uQuality = 1; // Avoid divide by zero. } } return uQuality; }
/** Advance the iterator to the next entry */ bool DirectoryEntryIterator::nextEntry (LedgerEntrySet& les) { if (!mDirNode) { WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << to_string (mRootIndex) << ") need dir node"; // Are we already at the end if (mDirIndex.isZero()) { WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << to_string (mRootIndex) << ") at end"; return false; } // Fetch the current directory mDirNode = les.entryCache (ltDIR_NODE, mRootIndex); if (!mDirNode) { WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << to_string (mRootIndex) << ") no dir node"; mEntryIndex.zero(); return false; } } if (!les.dirNext (mRootIndex, mDirNode, mEntry, mEntryIndex)) { mDirIndex.zero(); mDirNode.reset(); WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << to_string (mRootIndex) << ") now at end"; return false; } WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << to_string (mRootIndex) << ") now at " << mEntry; return true; }
STAmount creditLimit ( LedgerEntrySet& ledger, AccountID const& account, AccountID const& issuer, Currency const& currency) { STAmount result ({currency, account}); auto sleDivvyState = ledger.entryCache (ltRIPPLE_STATE, getDivvyStateIndex (account, issuer, currency)); if (sleDivvyState) { result = sleDivvyState->getFieldAmount ( account < issuer ? sfLowLimit : sfHighLimit); result.setIssuer (account); } assert (result.getIssuer () == account); assert (result.getCurrency () == currency); return result; }
// 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 (); }
// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { RPC::LegacyPathFind lpf (context.role == Role::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); context.loadType = Resource::feeHighBurdenRPC; RippleAddress raSrc; RippleAddress raDst; STAmount saDstAmount; Ledger::pointer lpLedger; Json::Value jvResult; if (getConfig().RUN_STANDALONE || context.params.isMember(jss::ledger) || context.params.isMember(jss::ledger_index) || context.params.isMember(jss::ledger_hash)) { // The caller specified a ledger jvResult = RPC::lookupLedger ( context.params, lpLedger, context.netOps); if (!lpLedger) return jvResult; } if (!context.params.isMember ("source_account")) { jvResult = rpcError (rpcSRC_ACT_MISSING); } else if (!context.params["source_account"].isString () || !raSrc.setAccountID ( context.params["source_account"].asString ())) { jvResult = rpcError (rpcSRC_ACT_MALFORMED); } else if (!context.params.isMember ("destination_account")) { jvResult = rpcError (rpcDST_ACT_MISSING); } else if (!context.params["destination_account"].isString () || !raDst.setAccountID ( context.params["destination_account"].asString ())) { jvResult = rpcError (rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. !context.params.isMember ("destination_amount") || ! amountFromJsonNoThrow(saDstAmount, context.params["destination_amount"]) || saDstAmount <= zero || (!isXRP(saDstAmount.getCurrency ()) && (!saDstAmount.getIssuer () || noAccount() == saDstAmount.getIssuer ()))) { WriteLog (lsINFO, RPCHandler) << "Bad destination_amount."; jvResult = rpcError (rpcINVALID_PARAMS); } else if ( // Checks on source_currencies. context.params.isMember ("source_currencies") && (!context.params["source_currencies"].isArray () || !context.params["source_currencies"].size ()) // Don't allow empty currencies. ) { WriteLog (lsINFO, RPCHandler) << "Bad source_currencies."; jvResult = rpcError (rpcINVALID_PARAMS); } else { context.loadType = Resource::feeHighBurdenRPC; RippleLineCache::pointer cache; if (lpLedger) { // The caller specified a ledger lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false); cache = std::make_shared<RippleLineCache>(lpLedger); } else { // The closed ledger is recent and any nodes made resident // have the best chance to persist lpLedger = context.netOps.getClosedLedger(); cache = getApp().getPathRequests().getLineCache(lpLedger, false); } Json::Value jvSrcCurrencies; if (context.params.isMember ("source_currencies")) { jvSrcCurrencies = context.params["source_currencies"]; } else { auto currencies = accountSourceCurrencies (raSrc, cache, true); jvSrcCurrencies = Json::Value (Json::arrayValue); for (auto const& uCurrency: currencies) { Json::Value jvCurrency (Json::objectValue); jvCurrency["currency"] = to_string(uCurrency); jvSrcCurrencies.append (jvCurrency); } } // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); // TODO(tom): this could be optimized the same way that // PathRequest::doUpdate() is - if we don't obsolete this code first. auto usDestCurrID = accountDestCurrencies (raDst, cache, true); for (auto const& uCurrency: usDestCurrID) jvDestCur.append (to_string (uCurrency)); jvResult["destination_currencies"] = jvDestCur; jvResult["destination_account"] = raDst.humanAccountID (); Json::Value jvArray (Json::arrayValue); int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) { ++level; } if (context.params.isMember("search_depth") && context.params["search_depth"].isIntegral()) { int rLev = context.params["search_depth"].asInt (); if ((rLev < level) || (context.role == Role::ADMIN)) level = rLev; } FindPaths fp ( cache, raSrc.getAccountID(), raDst.getAccountID(), saDstAmount, level, 4); // max paths for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; Currency uSrcCurrencyID; Account uSrcIssuerID; if (!jvSource.isObject ()) return rpcError (rpcINVALID_PARAMS); // Parse mandatory currency. if (!jvSource.isMember ("currency") || !to_currency ( uSrcCurrencyID, jvSource["currency"].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad currency."; return rpcError (rpcSRC_CUR_MALFORMED); } if (uSrcCurrencyID.isNonZero ()) uSrcIssuerID = raSrc.getAccountID (); // Parse optional issuer. if (jvSource.isMember ("issuer") && ((!jvSource["issuer"].isString () || !to_issuer (uSrcIssuerID, jvSource["issuer"].asString ())) || (uSrcIssuerID.isZero () != uSrcCurrencyID.isZero ()) || (noAccount() == uSrcIssuerID))) { WriteLog (lsINFO, RPCHandler) << "Bad issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } STPathSet spsComputed; if (context.params.isMember("paths")) { Json::Value pathSet = Json::objectValue; pathSet["Paths"] = context.params["paths"]; STParsedJSONObject paths ("pathSet", pathSet); if (paths.object.get() == nullptr) return paths.error; else { spsComputed = paths.object.get()->getFieldPathSet (sfPaths); WriteLog (lsTRACE, RPCHandler) << "ripple_path_find: Paths: " << spsComputed.getJson (0); } } STPath fullLiquidityPath; auto valid = fp.findPathsForIssue ( {uSrcCurrencyID, uSrcIssuerID}, spsComputed, fullLiquidityPath); if (!valid) { WriteLog (lsWARNING, RPCHandler) << "ripple_path_find: No paths found."; } else { auto& issuer = isXRP (uSrcIssuerID) ? isXRP (uSrcCurrencyID) ? // Default to source account. xrpAccount() : Account (raSrc.getAccountID ()) : uSrcIssuerID; // Use specifed issuer. STAmount saMaxAmount ({uSrcCurrencyID, issuer}, 1); saMaxAmount.negate (); LedgerEntrySet lesSandbox (lpLedger, tapNONE); auto rc = path::RippleCalc::rippleCalculate ( lesSandbox, saMaxAmount, // --> Amount to send is unlimited // to get an estimate. saDstAmount, // --> Amount to deliver. raDst.getAccountID (), // --> Account to deliver to. raSrc.getAccountID (), // --> Account sending from. spsComputed); // --> Path set. WriteLog (lsWARNING, RPCHandler) << "ripple_path_find:" << " saMaxAmount=" << saMaxAmount << " saDstAmount=" << saDstAmount << " saMaxAmountAct=" << rc.actualAmountIn << " saDstAmountAct=" << rc.actualAmountOut; if (fullLiquidityPath.size() > 0 && (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL)) { WriteLog (lsDEBUG, PathRequest) << "Trying with an extra path element"; spsComputed.push_back (fullLiquidityPath); lesSandbox.clear (); rc = path::RippleCalc::rippleCalculate ( lesSandbox, saMaxAmount, // --> Amount to send is unlimited // to get an estimate. saDstAmount, // --> Amount to deliver. raDst.getAccountID (), // --> Account to deliver to. raSrc.getAccountID (), // --> Account sending from. spsComputed); // --> Path set. WriteLog (lsDEBUG, PathRequest) << "Extra path element gives " << transHuman (rc.result ()); } if (rc.result () == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); STPathSet spsCanonical; // Reuse the expanded as it would need to be calcuated // anyway to produce the canonical. (At least unless we // make a direct canonical.) jvEntry["source_amount"] = rc.actualAmountIn.getJson (0); jvEntry["paths_canonical"] = Json::arrayValue; jvEntry["paths_computed"] = spsComputed.getJson (0); jvArray.append (jvEntry); } else { std::string strToken; std::string strHuman; transResultInfo (rc.result (), strToken, strHuman); WriteLog (lsDEBUG, RPCHandler) << "ripple_path_find: " << strToken << " " << strHuman << " " << spsComputed.getJson (0); } } } // Each alternative differs by source currency. jvResult["alternatives"] = jvArray; } WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("ripple_path_find< %s") % jvResult); return jvResult; }
void pathNext ( RippleCalc& rippleCalc, PathState& pathState, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) { // The next state is what is available in preference order. // This is calculated when referenced accounts changed. pathState.clear(); WriteLog (lsTRACE, RippleCalc) << "pathNext: Path In: " << pathState.getJson (); assert (pathState.nodes().size () >= 2); lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint. for (unsigned int uIndex = pathState.nodes().size (); uIndex--;) { auto& node = pathState.nodes()[uIndex]; node.saRevRedeem.clear (); node.saRevIssue.clear (); node.saRevDeliver.clear (); node.saFwdDeliver.clear (); } pathState.setStatus(computeReverseLiquidity ( rippleCalc, pathState, bMultiQuality)); WriteLog (lsTRACE, RippleCalc) << "pathNext: Path after reverse: " << pathState.getJson (); if (tesSUCCESS == pathState.status()) { // Do forward. lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint. pathState.setStatus(computeForwardLiquidity ( rippleCalc, pathState, bMultiQuality)); } if (tesSUCCESS == pathState.status()) { CondLog (!pathState.inPass() || !pathState.outPass(), lsDEBUG, RippleCalc) << "pathNext: Error computeForwardLiquidity reported success for nothing:" << " saOutPass="******" inPass()=" << pathState.inPass(); if (!pathState.outPass() || !pathState.inPass()) throw std::runtime_error ("Made no progress."); // Calculate relative quality. pathState.setQuality(STAmount::getRate ( pathState.outPass(), pathState.inPass())); WriteLog (lsTRACE, RippleCalc) << "pathNext: Path after forward: " << pathState.getJson (); } else { pathState.setQuality(0); } }
/** Get the current ledger entry */ SLE::pointer DirectoryEntryIterator::getEntry (LedgerEntrySet& les, LedgerEntryType type) { return les.entryCache (type, mEntryIndex); }