bool Log::ShouldLog(LogFilterType type, LogLevel level) const { LoggerMap::const_iterator it = loggers.begin(); while (it != loggers.end() && it->second.getType() != type) ++it; if (it != loggers.end()) { LogLevel loggerLevel = it->second.getLogLevel(); return loggerLevel && loggerLevel <= level; } if (type != LOG_FILTER_GENERAL) return ShouldLog(LOG_FILTER_GENERAL, level); return false; }
void Log::outCharDump(char const* str, uint32 accountId, uint32 guid, char const* name) { if (!str || !ShouldLog("entities.player.dump", LOG_LEVEL_INFO)) return; std::ostringstream ss; ss << "== START DUMP == (account: " << accountId << " guid: " << guid << " name: " << name << ")\n" << str << "\n== END DUMP ==\n"; LogMessage* msg = new LogMessage(LOG_LEVEL_INFO, "entities.player.dump", ss.str()); std::ostringstream param; param << guid << '_' << name; msg->param1 = param.str(); write(msg); }
void Log::outCharDump(char const* str, uint32 accountId, uint32 guid, char const* name) { if (!str || !ShouldLog(LOG_FILTER_PLAYER_DUMP, LOG_LEVEL_INFO)) return; std::ostringstream ss; ss << "== START DUMP == (account: " << accountId << " guid: " << guid << " name: " << name << ")\n" << str << "\n== END DUMP ==\n"; LogMessage* msg = new LogMessage(LOG_LEVEL_INFO, LOG_FILTER_PLAYER_DUMP, ss.str()); ss.clear(); ss << guid << '_' << name; msg->param1 = ss.str(); write(msg); }
void Log::outCommand(uint32 account, const char * str, ...) { if (!str || !ShouldLog(LOG_FILTER_GMCOMMAND, LOG_LEVEL_INFO)) return; va_list ap; va_start(ap, str); char text[MAX_QUERY_LEN]; vsnprintf(text, MAX_QUERY_LEN, str, ap); va_end(ap); LogMessage* msg = new LogMessage(LOG_LEVEL_INFO, LOG_FILTER_GMCOMMAND, text); std::ostringstream ss; ss << account; msg->param1 = ss.str(); write(msg); }
/** * Emit lsTRACE-level logging of a count of the objects in a * ledger's account-state SHAMap, grouped by LedgerEntryType. */ static void traceLedgerContents(Ledger& ledger) { if (ShouldLog (lsTRACE, LedgerDump)) { std::map<LedgerEntryType, int> entryTypeCounts; ledger.peekAccountStateMap ()->visitLeaves ( [&](SHAMapItem::ref item) { uint256 idx = item->getTag (); Serializer ser = item->peekSerializer (); SerializedLedgerEntry sle (ser, idx); entryTypeCounts[sle.getType ()]++; }); WriteLog (lsTRACE, LedgerDump) << "Entry-type counts in account state map:"; for (auto const& etc : entryTypeCounts) { WriteLog (lsTRACE, LedgerDump) << entryTypeNames[etc.first] << ": " << etc.second; } } }
/** * Instantiate an application and replay a ledger history out * of the dump file `filename`. */ void LedgerDump::loadTransactions (std::string const& filename) { std::ifstream in (filename); require ((bool)in, "opening file"); std::unique_ptr <Application> app (make_Application ()); app->setup (); auto &lm = app->getLedgerMaster (); WriteLog (lsINFO, LedgerDump) << "Loading ledgers from " << filename; auto nTxs = 0; // app->setup() when called with START_UP == Config::FRESH calls // ApplicationImp::startNewLedger(). Unfortunately it starts the new // ledger at the wrong timestamp, so we call it again once we've read // the first ledger we're going to apply. However, it's worth // understanding that startNewLedger() establishes 3 ledgers: // // Ledger 0 is the genesis ledger, it's not a real ledger, just a // number. // // Ledger 1 is the root-account deposit ledger, with a single pile of // currency owned by a single account generated by the seed // "masterpassword". // // Ledger 2 is created and closed immediately on start, not sure why. // // Ledger 3 is a new ledger chained to #2 and left open in // ledgermaster. // // Ledger 3 is where replay should be starting, so (once we call // startNewLedger() again) we pull out ledger #2 and use it as the // parent of the new ledger 3 we're replaying, and throw away the #3 // that was made by startNewLedger(). Ledger::pointer parentLedger; while (in) { if ((gLedgerSeq & 0xfff) == 0) { Job j; app->doSweep (j); } Json::Value j = loadJsonRecord (in); Ledger::pointer deserializedLedger; SHAMap::pointer txSet; std::vector<uint256> txOrder; std::tie (deserializedLedger, txSet, txOrder) = loadLedgerAndTransactionsFromJSON (*app, j); if (!parentLedger) { if (getConfig ().START_LEDGER.empty ()) { require (deserializedLedger->getLedgerSeq () == 3, "Initial ledger isn't seq #3"); // On first iteration, restart the app's view of the ledger // history at the same instant as the parent close time of the // first ledger (ledger #3). app->startNewLedger (deserializedLedger->getParentCloseTimeNC ()); parentLedger = lm.getClosedLedger (); require (parentLedger->getLedgerSeq () == 2, "Initial ledger parent isn't seq #2"); } else { // We're being invoked with --ledger, which is where we // will start replay from. require (app->loadOldLedger (getConfig ().START_LEDGER, false), "Reloading old ledger failed."); parentLedger = lm.getClosedLedger (); } auto const parentSeq = parentLedger->getLedgerSeq (); auto seq = j["seq"].asUInt (); while (parentSeq + 1 > seq) { // Fast-scan JSON records until we hit the right one. WriteLog (lsINFO, LedgerDump) << "scanning past ledger: " << seq; j = loadJsonRecord (in); seq = j["seq"].asUInt (); if (parentSeq + 1 <= seq) { require (parentSeq + 1 == seq, "Missing ledgers between loaded and replay-start"); std::tie (deserializedLedger, txSet, txOrder) = loadLedgerAndTransactionsFromJSON (*app, j); } } gLedgerSeq = parentSeq; require(parentLedger->getLedgerSeq () + 1 == deserializedLedger->getLedgerSeq (), "Mismatched ledger-sequence prefix."); } Ledger::pointer currentLedger = boost::make_shared<Ledger> (true, *parentLedger); currentLedger->setCloseTime (deserializedLedger->getCloseTimeNC ()); currentLedger->setCloseFlags (deserializedLedger->getCloseFlags ()); currentLedger->setParentHash (deserializedLedger->getParentHash ()); WriteLog (lsINFO, LedgerDump) << "loading ledger: " << currentLedger->getLedgerSeq(); if (ShouldLog (lsTRACE, LedgerDump)) { WriteLog (lsTRACE, LedgerDump) << "expecting next ledger:"; WriteLog (lsTRACE, LedgerDump) << deserializedLedger->getJson (0); WriteLog (lsTRACE, LedgerDump) << "synthetic next ledger:"; WriteLog (lsTRACE, LedgerDump) << currentLedger->getJson (0); } gLedgerSeq++; // Apply transactions, transitioning from one ledger state to next. WriteLog (lsDEBUG, LedgerDump) << "Applying set of " << txOrder.size() << " transactions"; CanonicalTXSet retriableTransactions (txSet->getHash ()); std::set<uint256> failedTransactions; LedgerConsensus::applyTransactions (txSet, currentLedger, currentLedger, retriableTransactions, failedTransactions, false, txOrder); require (retriableTransactions.empty (), "failed retriable tx set is not empty"); require (failedTransactions.empty (), "failed tx set is not empty"); currentLedger->updateSkipList (); currentLedger->setClosed (); currentLedger->setCloseTime (deserializedLedger->getCloseTimeNC ()); int asf = currentLedger->peekAccountStateMap ()->flushDirty ( hotACCOUNT_NODE, currentLedger->getLedgerSeq()); int tmf = currentLedger->peekTransactionMap ()->flushDirty ( hotTRANSACTION_NODE, currentLedger->getLedgerSeq()); nTxs += tmf; WriteLog (lsDEBUG, LedgerDump) << "Flushed " << asf << " account " << "and " << tmf << "transaction nodes"; // Finalize with the LedgerMaster. currentLedger->setAccepted (); bool alreadyHadLedger = lm.storeLedger (currentLedger); assert (! alreadyHadLedger); lm.pushLedger (currentLedger); WriteLog (lsTRACE, LedgerDump) << "parent ledger:"; traceLedgerContents (*parentLedger); WriteLog (lsTRACE, LedgerDump) << "current ledger:"; traceLedgerContents (*currentLedger); try { checkLedgersEqual (*deserializedLedger, *currentLedger); } catch (...) { WriteLog (lsINFO, LedgerDump) << "bad ledger:"; std::cerr << currentLedger->getJson (LEDGER_JSON_FULL); throw; } parentLedger = currentLedger; } WriteLog (lsINFO, LedgerDump) << "Loaded " << gLedgerSeq << "ledgers, " << nTxs << " transactions from " << filename; exit (0); }
// <-- TER: Only returns tepPATH_PARTIAL if partialPaymentAllowed. TER RippleCalc::rippleCalculate () { JLOG (j_.trace) << "rippleCalc>" << " saMaxAmountReq_:" << saMaxAmountReq_ << " saDstAmountReq_:" << saDstAmountReq_; TER resultCode = temUNCERTAIN; permanentlyUnfundedOffers_.clear (); mumSource_.clear (); // YYY Might do basic checks on src and dst validity as per doPayment. // Incrementally search paths. if (inputFlags.defaultPathsAllowed) { if (!addPathState (STPath(), resultCode)) return resultCode; } else if (spsPaths_.empty ()) { JLOG (j_.debug) << "rippleCalc: Invalid transaction:" << "No paths and direct ripple not allowed."; return temRIPPLE_EMPTY; } // Build a default path. Use saDstAmountReq_ and saMaxAmountReq_ to imply // nodes. // XXX Might also make a XRP bridge by default. JLOG (j_.trace) << "rippleCalc: Paths in set: " << spsPaths_.size (); // Now expand the path state. for (auto const& spPath: spsPaths_) { if (!addPathState (spPath, resultCode)) return resultCode; } if (resultCode != tesSUCCESS) return (resultCode == temUNCERTAIN) ? terNO_LINE : resultCode; resultCode = temUNCERTAIN; actualAmountIn_ = saMaxAmountReq_.zeroed(); actualAmountOut_ = saDstAmountReq_.zeroed(); // When processing, we don't want to complicate directory walking with // deletion. const std::uint64_t uQualityLimit = inputFlags.limitQuality ? getRate (saDstAmountReq_, saMaxAmountReq_) : 0; // Offers that became unfunded. std::set<uint256> unfundedOffersFromBestPaths; int iPass = 0; while (resultCode == temUNCERTAIN) { int iBest = -1; int iDry = 0; // True, if ever computed multi-quality. bool multiQuality = false; // Find the best path. for (auto pathState : pathStateList_) { if (pathState->quality()) // Only do active paths. { // If computing the only non-dry path, compute multi-quality. multiQuality = ((pathStateList_.size () - iDry) == 1); // Update to current amount processed. pathState->reset (actualAmountIn_, actualAmountOut_); // Error if done, output met. PathCursor pc(*this, *pathState, multiQuality, j_); pc.nextIncrement (); // Compute increment. JLOG (j_.debug) << "rippleCalc: AFTER:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()); if (!pathState->quality()) { // Path was dry. ++iDry; } else if (pathState->outPass() == zero) { // Path is not dry, but moved no funds // This should never happen. Consider the path dry JLOG (j_.warning) << "rippelCalc: Non-dry path moves no funds"; assert (false); pathState->setQuality (0); ++iDry; } else { CondLog (!pathState->inPass() || !pathState->outPass(), lsDEBUG, RippleCalc) << "rippleCalc: better:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"rippleCalc: better:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"rippleCalc: Summary:" << " Pass: "******" Dry: " << iDry << " Paths: " << pathStateList_.size (); for (auto pathState: pathStateList_) { JLOG (j_.debug) << "rippleCalc: " << "Summary: " << pathState->index() << " rate: " << amountFromRate (pathState->quality()) << " quality:" << pathState->quality() << " best: " << (iBest == pathState->index ()); } } if (iBest >= 0) { // Apply best path. auto pathState = pathStateList_[iBest]; JLOG (j_.debug) << "rippleCalc: best:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"rippleCalc: TOO MUCH:" << " actualAmountOut_:" << actualAmountOut_ << " saDstAmountReq_:" << saDstAmountReq_; return tefEXCEPTION; // TEMPORARY assert (false); } else if (actualAmountIn_ != saMaxAmountReq_ && iDry != pathStateList_.size ()) { // Have not met requested amount or max send, try to do // more. Prepare for next pass. // // Merge best pass' umReverse. mumSource_.insert ( pathState->reverse().begin (), pathState->reverse().end ()); if (iPass >= PAYMENT_MAX_LOOPS) { // This payment is taking too many passes JLOG (j_.error) << "rippleCalc: pass limit"; resultCode = telFAILED_PROCESSING; } } else if (!inputFlags.partialPaymentAllowed) { // Have sent maximum allowed. Partial payment not allowed. resultCode = tecPATH_PARTIAL; } else { // Have sent maximum allowed. Partial payment allowed. Success. resultCode = tesSUCCESS; } } // Not done and ran out of paths. else if (!inputFlags.partialPaymentAllowed) { // Partial payment not allowed. resultCode = tecPATH_PARTIAL; } // Partial payment ok. else if (!actualAmountOut_) { // No payment at all. resultCode = tecPATH_DRY; } else { // Don't apply any payment increments resultCode = tesSUCCESS; } } if (resultCode == tesSUCCESS) { auto viewJ = logs_.journal ("View"); resultCode = deleteOffers(view, unfundedOffersFromBestPaths, viewJ); if (resultCode == tesSUCCESS) resultCode = deleteOffers(view, permanentlyUnfundedOffers_, viewJ); } // If isOpenLedger, then ledger is not final, can vote no. if (resultCode == telFAILED_PROCESSING && !inputFlags.isLedgerOpen) return tecFAILED_PROCESSING; return resultCode; }
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::string HTTPReply (int nStatus, std::string const& strMsg) { if (ShouldLog (lsTRACE, RPC)) { WriteLog (lsTRACE, RPC) << "HTTP Reply " << nStatus << " " << strMsg; } std::string ret; if (nStatus == 401) { ret.reserve (512); ret.append ("HTTP/1.0 401 Authorization Required\r\n"); ret.append (getHTTPHeaderTimestamp ()); // CHECKME this returns a different version than the replies below. Is // this by design or an accident or should it be using // BuildInfo::getFullVersionString () as well? ret.append ("Server: " SYSTEM_NAME "-json-rpc/"); ret.append (FormatFullVersion ()); ret.append ("\r\n"); // Be careful in modifying this! If you change the contents you MUST // update the Content-Length header as well to indicate the correct // size of the data. ret.append ("WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" "Content-Type: text/html\r\n" "Content-Length: 296\r\n" "\r\n" "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" "<HTML>\r\n" "<HEAD>\r\n" "<TITLE>Error</TITLE>\r\n" "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" "</HEAD>\r\n" "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"); return ret; } ret.reserve(256 + strMsg.length()); switch (nStatus) { case 200: ret.append ("HTTP/1.1 200 OK\r\n"); break; case 400: ret.append ("HTTP/1.1 400 Bad Request\r\n"); break; case 403: ret.append ("HTTP/1.1 403 Forbidden\r\n"); break; case 404: ret.append ("HTTP/1.1 404 Not Found\r\n"); break; case 500: ret.append ("HTTP/1.1 500 Internal Server Error\r\n"); break; } ret.append (getHTTPHeaderTimestamp ()); ret.append ("Connection: Keep-Alive\r\n"); if (getConfig ().RPC_ALLOW_REMOTE) ret.append ("Access-Control-Allow-Origin: *\r\n"); ret.append ("Content-Length: "); ret.append (std::to_string(strMsg.size () + 2)); ret.append ("\r\n"); ret.append ("Content-Type: application/json; charset=UTF-8\r\n"); ret.append ("Server: " SYSTEM_NAME "-json-rpc/"); ret.append (BuildInfo::getFullVersionString ()); ret.append ("\r\n"); ret.append ("\r\n"); ret.append (strMsg); ret.append ("\r\n"); return ret; }
// <-- TER: Only returns tepPATH_PARTIAL if partialPaymentAllowed. TER DivvyCalc::divvyCalculate () { assert (mActiveLedger.isValid ()); WriteLog (lsTRACE, DivvyCalc) << "divvyCalc>" << " saMaxAmountReq_:" << saMaxAmountReq_ << " saDstAmountReq_:" << saDstAmountReq_; TER resultCode = temUNCERTAIN; permanentlyUnfundedOffers_.clear (); mumSource_.clear (); // YYY Might do basic checks on src and dst validity as per doPayment. // Incrementally search paths. if (inputFlags.defaultPathsAllowed) { if (!addPathState (STPath(), resultCode)) return resultCode; } else if (spsPaths_.empty ()) { WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: Invalid transaction:" << "No paths and direct divvy not allowed."; return temRIPPLE_EMPTY; } // Build a default path. Use saDstAmountReq_ and saMaxAmountReq_ to imply // nodes. // XXX Might also make a XDV bridge by default. WriteLog (lsTRACE, DivvyCalc) << "divvyCalc: Paths in set: " << spsPaths_.size (); // Now expand the path state. for (auto const& spPath: spsPaths_) { if (!addPathState (spPath, resultCode)) return resultCode; } if (resultCode != tesSUCCESS) return (resultCode == temUNCERTAIN) ? terNO_LINE : resultCode; resultCode = temUNCERTAIN; actualAmountIn_ = saMaxAmountReq_.zeroed(); actualAmountOut_ = saDstAmountReq_.zeroed(); // When processing, we don't want to complicate directory walking with // deletion. const std::uint64_t uQualityLimit = inputFlags.limitQuality ? getRate (saDstAmountReq_, saMaxAmountReq_) : 0; // Offers that became unfunded. OfferSet unfundedOffersFromBestPaths; int iPass = 0; while (resultCode == temUNCERTAIN) { int iBest = -1; LedgerEntrySet lesCheckpoint = mActiveLedger; int iDry = 0; // True, if ever computed multi-quality. bool multiQuality = false; // Find the best path. for (auto pathState : pathStateList_) { if (pathState->quality()) // Only do active paths. { // If computing the only non-dry path, compute multi-quality. multiQuality = ((pathStateList_.size () - iDry) == 1); // Update to current amount processed. pathState->reset (actualAmountIn_, actualAmountOut_); // Error if done, output met. PathCursor pc(*this, *pathState, multiQuality); pc.nextIncrement (lesCheckpoint); // Compute increment. WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: AFTER:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()); if (!pathState->quality()) { // Path was dry. ++iDry; } else if (pathState->outPass() == zero) { // Path is not dry, but moved no funds // This should never happen. Consider the path dry WriteLog (lsWARNING, DivvyCalc) << "rippelCalc: Non-dry path moves no funds"; assert (false); pathState->setQuality (0); ++iDry; } else { CondLog (!pathState->inPass() || !pathState->outPass(), lsDEBUG, DivvyCalc) << "divvyCalc: better:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"divvyCalc: better:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"divvyCalc: Summary:" << " Pass: "******" Dry: " << iDry << " Paths: " << pathStateList_.size (); for (auto pathState: pathStateList_) { WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: " << "Summary: " << pathState->index() << " rate: " << amountFromRate (pathState->quality()) << " quality:" << pathState->quality() << " best: " << (iBest == pathState->index ()); } } if (iBest >= 0) { // Apply best path. auto pathState = pathStateList_[iBest]; WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: best:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"divvyCalc: TOO MUCH:" << " actualAmountOut_:" << actualAmountOut_ << " saDstAmountReq_:" << saDstAmountReq_; return tefEXCEPTION; // TEMPORARY assert (false); } else if (actualAmountIn_ != saMaxAmountReq_ && iDry != pathStateList_.size ()) { // Have not met requested amount or max send, try to do // more. Prepare for next pass. // // Merge best pass' umReverse. mumSource_.insert ( pathState->reverse().begin (), pathState->reverse().end ()); if (iPass >= PAYMENT_MAX_LOOPS) { // This payment is taking too many passes WriteLog (lsERROR, DivvyCalc) << "divvyCalc: pass limit"; resultCode = telFAILED_PROCESSING; } } else if (!inputFlags.partialPaymentAllowed) { // Have sent maximum allowed. Partial payment not allowed. resultCode = tecPATH_PARTIAL; } else { // Have sent maximum allowed. Partial payment allowed. Success. resultCode = tesSUCCESS; } } // Not done and ran out of paths. else if (!inputFlags.partialPaymentAllowed) { // Partial payment not allowed. resultCode = tecPATH_PARTIAL; } // Partial payment ok. else if (!actualAmountOut_) { // No payment at all. resultCode = tecPATH_DRY; } else { // We must restore the activeLedger from lesCheckpoint in the case // when iBest is -1 and just before the result is set to tesSUCCESS. mActiveLedger.swapWith (lesCheckpoint); resultCode = tesSUCCESS; } } if (resultCode == tesSUCCESS) { resultCode = deleteOffers(mActiveLedger, unfundedOffersFromBestPaths); if (resultCode == tesSUCCESS) resultCode = deleteOffers(mActiveLedger, permanentlyUnfundedOffers_); } // If isOpenLedger, then ledger is not final, can vote no. if (resultCode == telFAILED_PROCESSING && !inputFlags.isLedgerOpen) return tecFAILED_PROCESSING; return resultCode; }