ApplyResult applyTransaction (Application& app, OpenView& view, STTx const& txn, bool retryAssured, ApplyFlags flags, beast::Journal j) { // Returns false if the transaction has need not be retried. if (retryAssured) flags = flags | tapRETRY; JLOG (j.debug()) << "TXN " << txn.getTransactionID () //<< (engine.view().open() ? " open" : " closed") // because of the optional in engine << (retryAssured ? "/retry" : "/final"); try { auto const result = apply(app, view, txn, flags, j); if (result.second) { JLOG (j.debug()) << "Transaction applied: " << transHuman (result.first); return ApplyResult::Success; } if (isTefFailure (result.first) || isTemMalformed (result.first) || isTelLocal (result.first)) { // failure JLOG (j.debug()) << "Transaction failure: " << transHuman (result.first); return ApplyResult::Fail; } JLOG (j.debug()) << "Transaction retry: " << transHuman (result.first); return ApplyResult::Retry; } catch (std::exception const&) { JLOG (j.warn()) << "Throws"; return ApplyResult::Fail; } }
TER SetSignerList::replaceSignerList () { auto const accountKeylet = keylet::account (account_); auto const ownerDirKeylet = keylet::ownerDir (account_); auto const signerListKeylet = keylet::signers (account_); // This may be either a create or a replace. Preemptively remove any // old signer list. May reduce the reserve, so this is done before // checking the reserve. if (TER const ter = removeSignersFromLedger ( accountKeylet, ownerDirKeylet, signerListKeylet)) return ter; auto const sle = view().peek(accountKeylet); // Compute new reserve. Verify the account has funds to meet the reserve. auto const oldOwnerCount = (*sle)[sfOwnerCount]; std::uint32_t const addedOwnerCount = ownerCountDelta (signers_.size ()); auto const newReserve = view().fees().accountReserve(oldOwnerCount + addedOwnerCount); // We check the reserve against the starting balance because we want to // allow dipping into the reserve to pay fees. This behavior is consistent // with CreateTicket. if (mPriorBalance < newReserve) return tecINSUFFICIENT_RESERVE; // Everything's ducky. Add the ltSIGNER_LIST to the ledger. auto signerList = std::make_shared<SLE>(signerListKeylet); view().insert (signerList); writeSignersToSLE (signerList); auto viewJ = ctx_.app.journal ("View"); // Add the signer list to the account's directory. std::uint64_t hint; TER result = dirAdd(ctx_.view (), hint, ownerDirKeylet.key, signerListKeylet.key, describeOwnerDir (account_), viewJ); JLOG(j_.trace) << "Create signer list for account " << toBase58(account_) << ": " << transHuman (result); if (result != tesSUCCESS) return result; signerList->setFieldU64 (sfOwnerNode, hint); // If we succeeded, the new entry counts against the creator's reserve. adjustOwnerCount(view(), sle, addedOwnerCount, viewJ); return result; }
void AcceptedLedgerTx::buildJson () { mJson = Json::objectValue; mJson[jss::transaction] = mTxn->getJson (0); if (mMeta) { mJson[jss::meta] = mMeta->getJson (0); mJson[jss::raw_meta] = strHex (mRawMeta); } mJson[jss::result] = transHuman (mResult); if (!mAffected.empty ()) { Json::Value& affected = (mJson[jss::affected] = Json::arrayValue); for (auto const& ra : mAffected) { affected.append (ra.humanAccountID ()); } } if (mTxn->getTxnType () == ttOFFER_CREATE) { auto const account (mTxn->getSourceAccount ().getAccountID ()); auto const amount (mTxn->getFieldAmount (sfTakerGets)); // If the offer create is not self funded then add the owner balance if (account != amount.issue ().account) { LedgerEntrySet les (mLedger, tapNONE, true); auto const ownerFunds (funds( les, account, amount, fhIGNORE_FREEZE)); mJson[jss::transaction][jss::owner_funds] = ownerFunds.getText (); } } }
TER doApply () override { assert (mTxnAccount); // A ticket counts against the reserve of the issuing account, but we check // the starting balance because we want to allow dipping into the reserve to // pay fees. auto const accountReserve (mEngine->getLedger ()->getReserve ( mTxnAccount->getFieldU32 (sfOwnerCount) + 1)); if (mPriorBalance.getNValue () < accountReserve) return tecINSUFFICIENT_RESERVE; std::uint32_t expiration (0); if (mTxn.isFieldPresent (sfExpiration)) { expiration = mTxn.getFieldU32 (sfExpiration); if (!expiration) { m_journal.warning << "Malformed ticket requestion: bad expiration"; return temBAD_EXPIRATION; } if (mEngine->getLedger ()->getParentCloseTimeNC () >= expiration) return tesSUCCESS; } SLE::pointer sleTicket = mEngine->entryCreate (ltTICKET, Ledger::getTicketIndex (mTxnAccountID, mTxn.getSequence ())); sleTicket->setFieldAccount (sfAccount, mTxnAccountID); sleTicket->setFieldU32 (sfSequence, mTxn.getSequence ()); if (expiration != 0) sleTicket->setFieldU32 (sfExpiration, expiration); if (mTxn.isFieldPresent (sfTarget)) { Account const target_account (mTxn.getFieldAccount160 (sfTarget)); SLE::pointer sleTarget = mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (target_account)); // Destination account does not exist. if (!sleTarget) return tecNO_TARGET; // The issuing account is the default account to which the ticket // applies so don't bother saving it if that's what's specified. if (target_account != mTxnAccountID) sleTicket->setFieldAccount (sfTarget, target_account); } std::uint64_t hint; auto describer = [&](SLE::pointer p, bool b) { Ledger::ownerDirDescriber(p, b, mTxnAccountID); }; TER result = mEngine->view ().dirAdd ( hint, Ledger::getOwnerDirIndex (mTxnAccountID), sleTicket->getIndex (), describer); m_journal.trace << "Creating ticket " << to_string (sleTicket->getIndex ()) << ": " << transHuman (result); if (result != tesSUCCESS) return result; sleTicket->setFieldU64(sfOwnerNode, hint); // If we succeeded, the new entry counts agains the creator's reserve. mEngine->view ().incrementOwnerCount (mTxnAccount); return result; }
// 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; }
Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) { m_journal.debug << iIdentifier << " update " << (fast ? "fast" : "normal"); ScopedLockType sl (mLock); if (!isValid (cache)) return jvStatus; jvStatus = Json::objectValue; auto sourceCurrencies = sciSourceCurrencies; if (sourceCurrencies.empty ()) { auto usCurrencies = usAccountSourceCurrencies (raSrcAccount, cache, true); bool sameAccount = raSrcAccount == raDstAccount; for (auto const& c: usCurrencies) { if (!sameAccount || (c != saDstAmount.getCurrency ())) { if (c.isZero ()) sourceCurrencies.insert (std::make_pair (c, xrpAccount())); else sourceCurrencies.insert (std::make_pair (c, raSrcAccount.getAccountID ())); } } } jvStatus["source_account"] = raSrcAccount.humanAccountID (); jvStatus["destination_account"] = raDstAccount.humanAccountID (); jvStatus["destination_amount"] = saDstAmount.getJson (0); if (!jvId.isNull ()) jvStatus["id"] = jvId; Json::Value jvArray = Json::arrayValue; int iLevel = iLastLevel; bool loaded = getApp().getFeeTrack().isLoadedLocal(); if (iLevel == 0) { // first pass if (loaded || fast) iLevel = getConfig().PATH_SEARCH_FAST; else iLevel = getConfig().PATH_SEARCH; } else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast) { // leaving fast pathfinding iLevel = getConfig().PATH_SEARCH; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } else if (bLastSuccess) { // decrement, if possible if (iLevel > getConfig().PATH_SEARCH || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))) --iLevel; } else { // adjust as needed if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX)) ++iLevel; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } m_journal.debug << iIdentifier << " processing at level " << iLevel; bool found = false; for (auto const& currIssuer: sourceCurrencies) { { STAmount test ({currIssuer.first, currIssuer.second}, 1); if (m_journal.debug) { m_journal.debug << iIdentifier << " Trying to find paths: " << test.getFullText (); } } bool valid; STPathSet& spsPaths = mContext[currIssuer]; Pathfinder pf (cache, raSrcAccount, raDstAccount, currIssuer.first, currIssuer.second, saDstAmount, valid); CondLog (!valid, lsDEBUG, PathRequest) << iIdentifier << " PF request not valid"; STPath extraPath; if (valid && pf.findPaths (iLevel, 4, spsPaths, extraPath)) { LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE); PathState::List pathStateList; STAmount saMaxAmountAct; STAmount saDstAmountAct; auto& account = currIssuer.second.isNonZero () ? Account(currIssuer.second) : isXRP (currIssuer.first) ? xrpAccount() : raSrcAccount.getAccountID (); STAmount saMaxAmount ({currIssuer.first, account}, 1); saMaxAmount.negate (); m_journal.debug << iIdentifier << " Paths found, calling rippleCalc"; TER resultCode = path::rippleCalculate ( lesSandbox, saMaxAmountAct, saDstAmountAct, pathStateList, saMaxAmount, saDstAmount, raDstAccount.getAccountID (), raSrcAccount.getAccountID (), spsPaths, false, false, false, true); if ((extraPath.size() > 0) && ((resultCode == terNO_LINE) || (resultCode == tecPATH_PARTIAL))) { m_journal.debug << iIdentifier << " Trying with an extra path element"; spsPaths.addPath(extraPath); pathStateList.clear (); resultCode = path::rippleCalculate ( lesSandbox, saMaxAmountAct, saDstAmountAct, pathStateList, saMaxAmount, saDstAmount, raDstAccount.getAccountID (), raSrcAccount.getAccountID (), spsPaths, false, false, false, true); m_journal.debug << iIdentifier << " Extra path element gives " << transHuman (resultCode); } if (resultCode == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); jvEntry["source_amount"] = saMaxAmountAct.getJson (0); jvEntry["paths_computed"] = spsPaths.getJson (0); found = true; jvArray.append (jvEntry); } else { m_journal.debug << iIdentifier << " rippleCalc returns " << transHuman (resultCode); } } else { m_journal.debug << iIdentifier << " No paths found"; } } iLastLevel = iLevel; bLastSuccess = found; if (fast && ptQuickReply.is_not_a_date_time()) { ptQuickReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFast ((ptQuickReply-ptCreated).total_milliseconds()); } else if (!fast && ptFullReply.is_not_a_date_time()) { ptFullReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFull ((ptFullReply-ptCreated).total_milliseconds()); } jvStatus["alternatives"] = jvArray; return jvStatus; }
TER TransactionEngine::applyTransaction ( STTx const& txn, TransactionEngineParams params, bool& didApply) { WriteLog (lsTRACE, TransactionEngine) << "applyTransaction>"; didApply = false; assert (mLedger); uint256 const& txID = txn.getTransactionID (); mNodes.init (mLedger, txID, mLedger->getLedgerSeq (), params); #ifdef BEAST_DEBUG if (1) { Serializer ser; txn.add (ser); SerializerIterator sit (ser); STTx s2 (sit); if (!s2.isEquivalent (txn)) { WriteLog (lsFATAL, TransactionEngine) << "Transaction serdes mismatch"; WriteLog (lsINFO, TransactionEngine) << txn.getJson (0); WriteLog (lsFATAL, TransactionEngine) << s2.getJson (0); assert (false); } } #endif if (!txID) { WriteLog (lsWARNING, TransactionEngine) << "applyTransaction: invalid transaction id"; return temINVALID; } TER terResult = Transactor::transact (txn, params, this); if (terResult == temUNKNOWN) { WriteLog (lsWARNING, TransactionEngine) << "applyTransaction: Invalid transaction: unknown transaction type"; return temUNKNOWN; } if (ShouldLog (lsDEBUG, TransactionEngine)) { std::string strToken; std::string strHuman; transResultInfo (terResult, strToken, strHuman); WriteLog (lsDEBUG, TransactionEngine) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; } if (isTesSuccess (terResult)) didApply = true; else if (isTecClaim (terResult) && !(params & tapRETRY)) { // only claim the transaction fee WriteLog (lsDEBUG, TransactionEngine) << "Reprocessing to only claim fee"; mNodes.clear (); SLE::pointer txnAcct = entryCache (ltACCOUNT_ROOT, getAccountRootIndex (txn.getSourceAccount ())); if (!txnAcct) terResult = terNO_ACCOUNT; else { std::uint32_t t_seq = txn.getSequence (); std::uint32_t a_seq = txnAcct->getFieldU32 (sfSequence); if (a_seq < t_seq) terResult = terPRE_SEQ; else if (a_seq > t_seq) terResult = tefPAST_SEQ; else { STAmount fee = txn.getTransactionFee (); STAmount balance = txnAcct->getFieldAmount (sfBalance); STAmount balanceVBC = txnAcct->getFieldAmount(sfBalanceVBC); // We retry/reject the transaction if the account // balance is zero or we're applying against an open // ledger and the balance is less than the fee if ((balance == zero) || (balanceVBC.getNValue() == 0) || ((params & tapOPEN_LEDGER) && (balance < fee))) { // Account has no funds or ledger is open terResult = terINSUF_FEE_B; } else { if (fee > balance) fee = balance; txnAcct->setFieldAmount (sfBalance, balance - fee); txnAcct->setFieldAmount(sfBalanceVBC, balanceVBC); txnAcct->setFieldU32 (sfSequence, t_seq + 1); entryModify (txnAcct); didApply = true; } } } } else WriteLog (lsDEBUG, TransactionEngine) << "Not applying transaction " << txID; if (didApply) { if (!checkInvariants (terResult, txn, params)) { WriteLog (lsFATAL, TransactionEngine) << "Transaction violates invariants"; WriteLog (lsFATAL, TransactionEngine) << txn.getJson (0); WriteLog (lsFATAL, TransactionEngine) << transToken (terResult) << ": " << transHuman (terResult); WriteLog (lsFATAL, TransactionEngine) << mNodes.getJson (0); didApply = false; terResult = tefINTERNAL; } else { // Transaction succeeded fully or (retries are not allowed and the // transaction could claim a fee) Serializer m; mNodes.calcRawMeta (m, terResult, mTxnSeq++); txnWrite (); Serializer s; txn.add (s); if (params & tapOPEN_LEDGER) { if (!mLedger->addTransaction (txID, s)) { WriteLog (lsFATAL, TransactionEngine) << "Tried to add transaction to open ledger that already had it"; assert (false); throw std::runtime_error ("Duplicate transaction applied"); } } else { if (!mLedger->addTransaction (txID, s, m)) { WriteLog (lsFATAL, TransactionEngine) << "Tried to add transaction to ledger that already had it"; assert (false); throw std::runtime_error ("Duplicate transaction applied to closed ledger"); } // Charge whatever fee they specified. STAmount saPaid = txn.getTransactionFee (); mLedger->destroyCoins (saPaid.getNValue ()); } } } mTxnAccount.reset (); mNodes.clear (); if (!(params & tapOPEN_LEDGER) && isTemMalformed (terResult)) { // XXX Malformed or failed transaction in closed ledger must bow out. } return terResult; }
std::pair<bool, Json::Value> ripplePathFind(RippleLineCache::pointer const& cache, RippleAddress const& raSrc, RippleAddress const& raDst, STAmount const& saDstAmount, Ledger::pointer const& lpLedger, Json::Value const& jvSrcCurrencies, boost::optional<Json::Value> const& contextPaths, int const& level) { FindPaths fp( cache, raSrc.getAccountID(), raDst.getAccountID(), saDstAmount, level, 4); // max paths Json::Value jvArray(Json::arrayValue); for (unsigned int i = 0; i != jvSrcCurrencies.size(); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; Currency uSrcCurrencyID; Account uSrcIssuerID; if (!jvSource.isObject()) return std::make_pair(false, rpcError(rpcINVALID_PARAMS)); // Parse mandatory currency. if (!jvSource.isMember(jss::currency) || !to_currency( uSrcCurrencyID, jvSource[jss::currency].asString())) { WriteLog(lsINFO, RPCHandler) << "Bad currency."; return std::make_pair(false, rpcError(rpcSRC_CUR_MALFORMED)); } if (uSrcCurrencyID.isNonZero()) uSrcIssuerID = raSrc.getAccountID(); // Parse optional issuer. if (jvSource.isMember(jss::issuer) && ((!jvSource[jss::issuer].isString() || !to_issuer(uSrcIssuerID, jvSource[jss::issuer].asString())) || (uSrcIssuerID.isZero() != uSrcCurrencyID.isZero()) || (noAccount() == uSrcIssuerID))) { WriteLog(lsINFO, RPCHandler) << "Bad issuer."; return std::make_pair(false, rpcError(rpcSRC_ISR_MALFORMED)); } STPathSet spsComputed; if (contextPaths) { Json::Value pathSet = Json::objectValue; pathSet[jss::Paths] = contextPaths.get(); STParsedJSONObject paths("pathSet", pathSet); if (paths.object.get() == nullptr) return std::make_pair(false, 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[jss::source_amount] = rc.actualAmountIn.getJson(0); jvEntry[jss::paths_canonical] = Json::arrayValue; jvEntry[jss::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); } } } return std::make_pair(true, jvArray); }
Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) { m_journal.debug << iIdentifier << " update " << (fast ? "fast" : "normal"); ScopedLockType sl (mLock); if (!isValid (cache)) return jvStatus; jvStatus = Json::objectValue; auto sourceCurrencies = sciSourceCurrencies; if (sourceCurrencies.empty ()) { auto usCurrencies = accountSourceCurrencies (*raSrcAccount, cache, true); bool sameAccount = *raSrcAccount == *raDstAccount; for (auto const& c: usCurrencies) { if (!sameAccount || (c != saDstAmount.getCurrency ())) { if (c.isZero ()) sourceCurrencies.insert ({c, xrpAccount()}); else sourceCurrencies.insert ({c, *raSrcAccount}); } } } if (hasCompletion ()) { // Old ripple_path_find API gives destination_currencies auto& destCurrencies = (jvStatus[jss::destination_currencies] = Json::arrayValue); auto usCurrencies = accountDestCurrencies (*raDstAccount, cache, true); for (auto const& c : usCurrencies) destCurrencies.append (to_string (c)); } jvStatus[jss::source_account] = getApp().accountIDCache().toBase58(*raSrcAccount); jvStatus[jss::destination_account] = getApp().accountIDCache().toBase58(*raDstAccount); jvStatus[jss::destination_amount] = saDstAmount.getJson (0); jvStatus[jss::full_reply] = ! fast; if (jvId) jvStatus["id"] = jvId; Json::Value jvArray = Json::arrayValue; int iLevel = iLastLevel; bool loaded = getApp().getFeeTrack().isLoadedLocal(); if (iLevel == 0) { // first pass if (loaded || fast) iLevel = getConfig().PATH_SEARCH_FAST; else iLevel = getConfig().PATH_SEARCH; } else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast) { // leaving fast pathfinding iLevel = getConfig().PATH_SEARCH; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } else if (bLastSuccess) { // decrement, if possible if (iLevel > getConfig().PATH_SEARCH || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))) --iLevel; } else { // adjust as needed if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX)) ++iLevel; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } m_journal.debug << iIdentifier << " processing at level " << iLevel; bool found = false; FindPaths fp ( cache, *raSrcAccount, *raDstAccount, saDstAmount, iLevel, 4); // iMaxPaths for (auto const& currIssuer: sourceCurrencies) { { STAmount test (currIssuer, 1); if (m_journal.debug) { m_journal.debug << iIdentifier << " Trying to find paths: " << test.getFullText (); } } STPathSet& spsPaths = mContext[currIssuer]; STPath fullLiquidityPath; auto valid = fp.findPathsForIssue ( currIssuer, spsPaths, fullLiquidityPath); CondLog (!valid, lsDEBUG, PathRequest) << iIdentifier << " PF request not valid"; if (valid) { boost::optional<PaymentSandbox> sandbox; sandbox.emplace(&*cache->getLedger(), tapNONE); auto& sourceAccount = !isXRP (currIssuer.account) ? currIssuer.account : isXRP (currIssuer.currency) ? xrpAccount() : *raSrcAccount; STAmount saMaxAmount ({currIssuer.currency, sourceAccount}, 1); saMaxAmount.negate (); m_journal.debug << iIdentifier << " Paths found, calling rippleCalc"; auto rc = path::RippleCalc::rippleCalculate ( *sandbox, saMaxAmount, saDstAmount, *raDstAccount, *raSrcAccount, spsPaths); if (!fullLiquidityPath.empty() && (rc.result () == terNO_LINE || rc.result () == tecPATH_PARTIAL)) { m_journal.debug << iIdentifier << " Trying with an extra path element"; spsPaths.push_back (fullLiquidityPath); sandbox.emplace(&*cache->getLedger(), tapNONE); rc = path::RippleCalc::rippleCalculate ( *sandbox, saMaxAmount, saDstAmount, *raDstAccount, *raSrcAccount, spsPaths); if (rc.result () != tesSUCCESS) m_journal.warning << iIdentifier << " Failed with covering path " << transHuman (rc.result ()); else m_journal.debug << iIdentifier << " Extra path element gives " << transHuman (rc.result ()); } if (rc.result () == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); rc.actualAmountIn.setIssuer (sourceAccount); jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0); jvEntry[jss::paths_computed] = spsPaths.getJson (0); if (hasCompletion ()) { // Old ripple_path_find API requires this jvEntry[jss::paths_canonical] = Json::arrayValue; } found = true; jvArray.append (jvEntry); } else { m_journal.debug << iIdentifier << " rippleCalc returns " << transHuman (rc.result ()); } } else { m_journal.debug << iIdentifier << " No paths found"; } } iLastLevel = iLevel; bLastSuccess = found; if (fast && ptQuickReply.is_not_a_date_time()) { ptQuickReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFast ((ptQuickReply-ptCreated).total_milliseconds()); } else if (!fast && ptFullReply.is_not_a_date_time()) { ptFullReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFull ((ptFullReply-ptCreated).total_milliseconds()); } jvStatus[jss::alternatives] = jvArray; return jvStatus; }