void DOMNodeIteratorImpl::removeNode (DOMNode* node) { if (fDetached) throw DOMException(DOMException::INVALID_STATE_ERR, 0, GetDOMNodeIteratorMemoryManager); // Implementation note: Fix-up means setting the current node properly // after a remove. if (!node) return; DOMNode* deleted = matchNodeOrParent(node); if (!deleted) return; if (fForward) { fCurrentNode = previousNode(deleted); } else // if (!fForward) { DOMNode* next = nextNode(deleted, false); if (next != 0) { // normal case: there _are_ nodes following this in the iterator. fCurrentNode = next; } else { // the last node in the iterator is to be removed, // so we set the current node to be the previous one. fCurrentNode = previousNode(deleted); fForward = true; } } }
bool SuffixMachine::onMatch() { #if 0 for (node_info * part=lastNode();part!=NULL;part=previousNode(part)) out<<"\n"<<info.text->mid(part->start,part->finish-part->start+1)<<"\n"; #endif //out<<"S:"<<info->word.mid(startingPos)<<"\n"; return controller->on_match_helper(); }
bool PrefixMachine::onMatch() { #if 0 for (node_info * part=lastNode();part!=NULL;part=previousNode(part)) out<<"\n"<<info.text->mid(part->start,part->finish-part->start+1)<<"\n"; #endif //out<<"p:"<<info->word.mid(0,position)<<"\n"; if (controller->Stem!=NULL) delete controller->Stem;//TODO: change this allocation, deallocation to clear controller->Stem=new StemMachine(controller,position,controller->Prefix->resulting_category_idOFCurrentMatch); controller->Stem->setSolutionSettings(controller->multi_p); return (*controller->Stem)(); }
//Removes the current node and transforms tree void BST::removeNode(int key) { //If its a leaf node just delete it and set it to NULL if(m_pLeft == NULL && m_pRight == NULL) { std::cout << "Case 1" << std::endl; delete m_pNode; m_pNode = NULL; } //If only right subtree, then replace current node with its right subtree else if( m_pLeft == NULL && m_pRight != NULL) { std::cout << "Case 2" << std::endl; BST* temp = m_pRight; m_pNode = m_pRight->m_pNode; m_pLeft = m_pRight->m_pLeft; m_pRight = m_pRight->m_pRight; delete temp; } //If only left subtree, then replace current node with its left subtree else if( m_pLeft != NULL && m_pRight == NULL) { std::cout << "Case 3" << std::endl; BST* temp = m_pLeft; m_pNode = m_pLeft->m_pNode; m_pRight = m_pLeft->m_pRight; m_pLeft = m_pLeft->m_pLeft; delete temp; } //If there are two subtrees then replace current node with previous node in key order else { std::cout << "Case 4" << std::endl; BST* previous = previousNode(); std::cout << "found previous" << std::endl; m_pNode = previous->m_pNode; m_pLeft = previous->m_pLeft; //delete previous; } }
TER PathCursor::forwardLiquidity () const { if (node().isAccount()) return forwardLiquidityForAccount (); // Otherwise, node is an offer. if (previousNode().account_ == zero) return tesSUCCESS; // Previous is an account node, resolve its deliver. STAmount saInAct; STAmount saInFees; auto resultCode = deliverNodeForward ( previousNode().account_, previousNode().saFwdDeliver, // Previous is sending this much. saInAct, saInFees); assert (resultCode != tesSUCCESS || previousNode().saFwdDeliver == saInAct + saInFees); return resultCode; }
DOM_Node NodeIteratorImpl::previousNode () { if (fDetached) throw DOM_DOMException(DOM_DOMException::INVALID_STATE_ERR, null); DOM_Node result; // if the root is null, or the current node is null, return null. if (fRoot.isNull() || fCurrentNode.isNull()) return result; DOM_Node aPreviousNode = fCurrentNode; bool accepted = false; while (!accepted) { if (fForward && ! aPreviousNode.isNull()) { //repeat last node. aPreviousNode = fCurrentNode; } else { // get previous node in backwards depth first order. aPreviousNode = previousNode(aPreviousNode); } // we are going backwards fForward = false; // if the new previous node is null, we're at head or past the root, // so return null. if (aPreviousNode.isNull()) return result; // check if node passes filters and whatToShow. accepted = acceptNode(aPreviousNode); if (accepted) { // if accepted, update the current node, and return it. fCurrentNode = aPreviousNode; return fCurrentNode; } } // there are no nodes? return result; }
DOMNode* DOMNodeIteratorImpl::previousNode () { if (fDetached) throw DOMException(DOMException::INVALID_STATE_ERR, 0, GetDOMNodeIteratorMemoryManager); // if the root is 0, or the current node is 0, return 0. if (!fRoot || !fCurrentNode) return 0; DOMNode* aPreviousNode = fCurrentNode; bool accepted = false; while (!accepted) { if (fForward && (aPreviousNode != 0)) { //repeat last node-> aPreviousNode = fCurrentNode; } else { // get previous node in backwards depth first order. aPreviousNode = previousNode(aPreviousNode); } // we are going backwards fForward = false; // if the new previous node is 0, we're at head or past the root, // so return 0. if (!aPreviousNode) return 0; // check if node passes filters and whatToShow. accepted = acceptNode(aPreviousNode); if (accepted) { // if accepted, update the current node, and return it. fCurrentNode = aPreviousNode; return fCurrentNode; } } // there are no nodes? return 0; }
TER PathCursor::deliverNodeForward ( AccountID const& uInAccountID, // --> Input owner's account. STAmount const& saInReq, // --> Amount to deliver. STAmount& saInAct, // <-- Amount delivered, this invocation. STAmount& saInFees, // <-- Fees charged, this invocation. bool callerHasLiquidity) const { TER resultCode = tesSUCCESS; // Don't deliver more than wanted. // Zeroed in reverse pass. node().directory.restart(multiQuality_); saInAct.clear (saInReq); saInFees.clear (saInReq); int loopCount = 0; auto viewJ = rippleCalc_.logs_.journal ("View"); // XXX Perhaps make sure do not exceed node().saRevDeliver as another way to // stop? while (resultCode == tesSUCCESS && saInAct + saInFees < saInReq) { // Did not spend all inbound deliver funds. if (++loopCount > (multiQuality_ ? CALC_NODE_DELIVER_MAX_LOOPS_MQ : CALC_NODE_DELIVER_MAX_LOOPS)) { JLOG (j_.warn()) << "deliverNodeForward: max loops cndf"; return telFAILED_PROCESSING; } // Determine values for pass to adjust saInAct, saInFees, and // node().saFwdDeliver. advanceNode (saInAct, false, callerHasLiquidity); // If needed, advance to next funded offer. if (resultCode != tesSUCCESS) { } else if (!node().offerIndex_) { JLOG (j_.warn()) << "deliverNodeForward: INTERNAL ERROR: Ran out of offers."; return telFAILED_PROCESSING; } else if (resultCode == tesSUCCESS) { auto const xferRate = effectiveRate ( previousNode().issue_, uInAccountID, node().offerOwnerAccount_, previousNode().transferRate_); // First calculate assuming no output fees: saInPassAct, // saInPassFees, saOutPassAct. // Offer maximum out - limited by funds with out fees. auto saOutFunded = std::min ( node().saOfferFunds, node().saTakerGets); // Offer maximum out - limit by most to deliver. auto saOutPassFunded = std::min ( saOutFunded, node().saRevDeliver - node().saFwdDeliver); // Offer maximum in - Limited by by payout. auto saInFunded = mulRound ( saOutPassFunded, node().saOfrRate, node().saTakerPays.issue (), true); // Offer maximum in with fees. auto saInTotal = multiplyRound ( saInFunded, xferRate, true); auto saInRemaining = saInReq - saInAct - saInFees; if (saInRemaining < beast::zero) saInRemaining.clear(); // In limited by remaining. auto saInSum = std::min (saInTotal, saInRemaining); // In without fees. auto saInPassAct = std::min ( node().saTakerPays, divideRound (saInSum, xferRate, true)); // Out limited by in remaining. auto outPass = divRound ( saInPassAct, node().saOfrRate, node().saTakerGets.issue (), true); STAmount saOutPassMax = std::min (saOutPassFunded, outPass); STAmount saInPassFeesMax = saInSum - saInPassAct; // Will be determined by next node(). STAmount saOutPassAct; // Will be determined by adjusted saInPassAct. STAmount saInPassFees; JLOG (j_.trace()) << "deliverNodeForward:" << " nodeIndex_=" << nodeIndex_ << " saOutFunded=" << saOutFunded << " saOutPassFunded=" << saOutPassFunded << " node().saOfferFunds=" << node().saOfferFunds << " node().saTakerGets=" << node().saTakerGets << " saInReq=" << saInReq << " saInAct=" << saInAct << " saInFees=" << saInFees << " saInFunded=" << saInFunded << " saInTotal=" << saInTotal << " saInSum=" << saInSum << " saInPassAct=" << saInPassAct << " saOutPassMax=" << saOutPassMax; // FIXME: We remove an offer if WE didn't want anything out of it? if (!node().saTakerPays || saInSum <= beast::zero) { JLOG (j_.debug()) << "deliverNodeForward: Microscopic offer unfunded."; // After math offer is effectively unfunded. pathState_.unfundedOffers().push_back (node().offerIndex_); node().bEntryAdvance = true; continue; } if (!saInFunded) { // Previous check should catch this. JLOG (j_.warn()) << "deliverNodeForward: UNREACHABLE REACHED"; // After math offer is effectively unfunded. pathState_.unfundedOffers().push_back (node().offerIndex_); node().bEntryAdvance = true; continue; } if (!isXRP(nextNode().account_)) { // ? --> OFFER --> account // Input fees: vary based upon the consumed offer's owner. // Output fees: none as XRP or the destination account is the // issuer. saOutPassAct = saOutPassMax; saInPassFees = saInPassFeesMax; JLOG (j_.trace()) << "deliverNodeForward: ? --> OFFER --> account:" << " offerOwnerAccount_=" << node().offerOwnerAccount_ << " nextNode().account_=" << nextNode().account_ << " saOutPassAct=" << saOutPassAct << " saOutFunded=" << saOutFunded; // Output: Debit offer owner, send XRP or non-XPR to next // account. resultCode = accountSend(view(), node().offerOwnerAccount_, nextNode().account_, saOutPassAct, viewJ); if (resultCode != tesSUCCESS) break; } else { // ? --> OFFER --> offer // // Offer to offer means current order book's output currency and // issuer match next order book's input current and issuer. // // Output fees: possible if issuer has fees and is not on either // side. STAmount saOutPassFees; // Output fees vary as the next nodes offer owners may vary. // Therefore, immediately push through output for current offer. resultCode = increment().deliverNodeForward ( node().offerOwnerAccount_, // --> Current holder. saOutPassMax, // --> Amount available. saOutPassAct, // <-- Amount delivered. saOutPassFees, // <-- Fees charged. saInAct > beast::zero); if (resultCode != tesSUCCESS) break; if (saOutPassAct == saOutPassMax) { // No fees and entire output amount. saInPassFees = saInPassFeesMax; } else { // Fraction of output amount. // Output fees are paid by offer owner and not passed to // previous. assert (saOutPassAct < saOutPassMax); auto inPassAct = mulRound ( saOutPassAct, node().saOfrRate, saInReq.issue (), true); saInPassAct = std::min (node().saTakerPays, inPassAct); auto inPassFees = multiplyRound ( saInPassAct, xferRate, true); saInPassFees = std::min (saInPassFeesMax, inPassFees); } // Do outbound debiting. // Send to issuer/limbo total amount including fees (issuer gets // fees). auto const& id = isXRP(node().issue_) ? xrpAccount() : node().issue_.account; auto outPassTotal = saOutPassAct + saOutPassFees; accountSend(view(), node().offerOwnerAccount_, id, outPassTotal, viewJ); JLOG (j_.trace()) << "deliverNodeForward: ? --> OFFER --> offer:" << " saOutPassAct=" << saOutPassAct << " saOutPassFees=" << saOutPassFees; } JLOG (j_.trace()) << "deliverNodeForward: " << " nodeIndex_=" << nodeIndex_ << " node().saTakerGets=" << node().saTakerGets << " node().saTakerPays=" << node().saTakerPays << " saInPassAct=" << saInPassAct << " saInPassFees=" << saInPassFees << " saOutPassAct=" << saOutPassAct << " saOutFunded=" << saOutFunded; // Funds were spent. node().bFundsDirty = true; // Do inbound crediting. // // Credit offer owner from in issuer/limbo (input transfer fees left // with owner). Don't attempt to have someone credit themselves, it // is redundant. if (isXRP (previousNode().issue_.currency) || uInAccountID != node().offerOwnerAccount_) { auto id = !isXRP(previousNode().issue_.currency) ? uInAccountID : xrpAccount(); resultCode = accountSend(view(), id, node().offerOwnerAccount_, saInPassAct, viewJ); if (resultCode != tesSUCCESS) break; } // Adjust offer. // // Fees are considered paid from a seperate budget and are not named // in the offer. STAmount saTakerGetsNew = node().saTakerGets - saOutPassAct; STAmount saTakerPaysNew = node().saTakerPays - saInPassAct; if (saTakerPaysNew < beast::zero || saTakerGetsNew < beast::zero) { JLOG (j_.warn()) << "deliverNodeForward: NEGATIVE:" << " saTakerPaysNew=" << saTakerPaysNew << " saTakerGetsNew=" << saTakerGetsNew; resultCode = telFAILED_PROCESSING; break; } node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew); node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew); view().update (node().sleOffer); if (saOutPassAct == saOutFunded || saTakerGetsNew == beast::zero) { // Offer became unfunded. JLOG (j_.debug()) << "deliverNodeForward: unfunded:" << " saOutPassAct=" << saOutPassAct << " saOutFunded=" << saOutFunded; pathState_.unfundedOffers().push_back (node().offerIndex_); node().bEntryAdvance = true; } else { if (saOutPassAct >= saOutFunded) { JLOG (j_.warn()) << "deliverNodeForward: TOO MUCH:" << " saOutPassAct=" << saOutPassAct << " saOutFunded=" << saOutFunded; } assert (saOutPassAct < saOutFunded); } saInAct += saInPassAct; saInFees += saInPassFees; // Adjust amount available to next node(). node().saFwdDeliver = std::min (node().saRevDeliver, node().saFwdDeliver + saOutPassAct); } } JLOG (j_.trace()) << "deliverNodeForward<" << " nodeIndex_=" << nodeIndex_ << " saInAct=" << saInAct << " saInFees=" << saInFees; return resultCode; }
// To deliver from an order book, when computing TER PathCursor::deliverNodeReverseImpl ( AccountID const& uOutAccountID, // --> Output owner's account. STAmount const& saOutReq, // --> Funds requested to be // delivered for an increment. STAmount& saOutAct, // <-- Funds actually delivered for an // increment bool callerHasLiquidity ) const { TER resultCode = tesSUCCESS; // Accumulation of what the previous node must deliver. // Possible optimization: Note this gets zeroed on each increment, ideally // only on first increment, then it could be a limit on the forward pass. saOutAct.clear (saOutReq); JLOG (j_.trace()) << "deliverNodeReverse>" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq << " saPrvDlvReq=" << previousNode().saRevDeliver; assert (saOutReq != zero); int loopCount = 0; auto viewJ = rippleCalc_.logs_.journal ("View"); // While we did not deliver as much as requested: while (saOutAct < saOutReq) { if (++loopCount > (multiQuality_ ? CALC_NODE_DELIVER_MAX_LOOPS_MQ : CALC_NODE_DELIVER_MAX_LOOPS)) { JLOG (j_.warn()) << "loop count exceeded"; return telFAILED_PROCESSING; } resultCode = advanceNode (saOutAct, true, callerHasLiquidity); // If needed, advance to next funded offer. if (resultCode != tesSUCCESS || !node().offerIndex_) // Error or out of offers. break; auto const xferRate = effectiveRate ( node().issue_, uOutAccountID, node().offerOwnerAccount_, node().transferRate_); JLOG (j_.trace()) << "deliverNodeReverse:" << " offerOwnerAccount_=" << node().offerOwnerAccount_ << " uOutAccountID=" << uOutAccountID << " node().issue_.account=" << node().issue_.account << " xferRate=" << xferRate; // Only use rate when not in multi-quality mode if (!multiQuality_) { if (!node().rateMax) { // Set initial rate. JLOG (j_.trace()) << "Set initial rate"; node().rateMax = xferRate; } else if (xferRate > node().rateMax) { // Offer exceeds initial rate. JLOG (j_.trace()) << "Offer exceeds initial rate: " << *node().rateMax; break; // Done. Don't bother looking for smaller transferRates. } else if (xferRate < node().rateMax) { // Reducing rate. Additional offers will only // be considered for this increment if they // are at least this good. // // At this point, the overall rate is reducing, // while the overall rate is not xferRate, it // would be wrong to add anything with a rate // above xferRate. // // The rate would be reduced if the current // offer was from the issuer and the previous // offer wasn't. JLOG (j_.trace()) << "Reducing rate: " << *node().rateMax; node().rateMax = xferRate; } } // Amount that goes to the taker. STAmount saOutPassReq = std::min ( std::min (node().saOfferFunds, node().saTakerGets), saOutReq - saOutAct); // Maximum out - assuming no out fees. STAmount saOutPassAct = saOutPassReq; // Amount charged to the offer owner. // // The fee goes to issuer. The fee is paid by offer owner and not passed // as a cost to taker. // // Round down: prefer liquidity rather than microscopic fees. STAmount saOutPlusFees = multiplyRound ( saOutPassAct, xferRate, false); // Offer out with fees. JLOG (j_.trace()) << "deliverNodeReverse:" << " saOutReq=" << saOutReq << " saOutAct=" << saOutAct << " node().saTakerGets=" << node().saTakerGets << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees << " node().saOfferFunds=" << node().saOfferFunds; if (saOutPlusFees > node().saOfferFunds) { // Offer owner can not cover all fees, compute saOutPassAct based on // node().saOfferFunds. saOutPlusFees = node().saOfferFunds; // Round up: prefer liquidity rather than microscopic fees. But, // limit by requested. auto fee = divideRound (saOutPlusFees, xferRate, true); saOutPassAct = std::min (saOutPassReq, fee); JLOG (j_.trace()) << "deliverNodeReverse: Total exceeds fees:" << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees << " node().saOfferFunds=" << node().saOfferFunds; } // Compute portion of input needed to cover actual output. auto outputFee = mulRound ( saOutPassAct, node().saOfrRate, node().saTakerPays.issue (), true); if (*stAmountCalcSwitchover == false && ! outputFee) { JLOG (j_.fatal()) << "underflow computing outputFee " << "saOutPassAct: " << saOutPassAct << " saOfrRate: " << node ().saOfrRate; return telFAILED_PROCESSING; } STAmount saInPassReq = std::min (node().saTakerPays, outputFee); STAmount saInPassAct; JLOG (j_.trace()) << "deliverNodeReverse:" << " outputFee=" << outputFee << " saInPassReq=" << saInPassReq << " node().saOfrRate=" << node().saOfrRate << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees; if (!saInPassReq) // FIXME: This is bogus { // After rounding did not want anything. JLOG (j_.debug()) << "deliverNodeReverse: micro offer is unfunded."; node().bEntryAdvance = true; continue; } // Find out input amount actually available at current rate. else if (!isXRP(previousNode().account_)) { // account --> OFFER --> ? // Due to node expansion, previous is guaranteed to be the issuer. // // Previous is the issuer and receiver is an offer, so no fee or // quality. // // Previous is the issuer and has unlimited funds. // // Offer owner is obtaining IOUs via an offer, so credit line limits // are ignored. As limits are ignored, don't need to adjust // previous account's balance. saInPassAct = saInPassReq; JLOG (j_.trace()) << "deliverNodeReverse: account --> OFFER --> ? :" << " saInPassAct=" << saInPassAct; } else { // offer --> OFFER --> ? // Compute in previous offer node how much could come in. // TODO(tom): Fix nasty recursion here! resultCode = increment(-1).deliverNodeReverseImpl( node().offerOwnerAccount_, saInPassReq, saInPassAct, saOutAct > zero); if (amendmentRIPD1141(view().info().parentCloseTime)) { // The recursive call is dry this time, but we have liquidity // from previous calls if (resultCode == tecPATH_DRY && saOutAct > zero) { resultCode = tesSUCCESS; break; } } JLOG (j_.trace()) << "deliverNodeReverse: offer --> OFFER --> ? :" << " saInPassAct=" << saInPassAct; } if (resultCode != tesSUCCESS) break; if (saInPassAct < saInPassReq) { // Adjust output to conform to limited input. auto outputRequirements = divRound (saInPassAct, node ().saOfrRate, node ().saTakerGets.issue (), true); saOutPassAct = std::min (saOutPassReq, outputRequirements); auto outputFees = multiplyRound (saOutPassAct, xferRate, true); saOutPlusFees = std::min (node().saOfferFunds, outputFees); JLOG (j_.trace()) << "deliverNodeReverse: adjusted:" << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees; } else { // TODO(tom): more logging here. assert (saInPassAct == saInPassReq); } // Funds were spent. node().bFundsDirty = true; // Want to deduct output to limit calculations while computing reverse. // Don't actually need to send. // // Sending could be complicated: could fund a previous offer not yet // visited. However, these deductions and adjustments are tenative. // // Must reset balances when going forward to perform actual transfers. resultCode = accountSend(view(), node().offerOwnerAccount_, node().issue_.account, saOutPassAct, viewJ); if (resultCode != tesSUCCESS) break; // Adjust offer STAmount saTakerGetsNew = node().saTakerGets - saOutPassAct; STAmount saTakerPaysNew = node().saTakerPays - saInPassAct; if (saTakerPaysNew < zero || saTakerGetsNew < zero) { JLOG (j_.warn()) << "deliverNodeReverse: NEGATIVE:" << " node().saTakerPaysNew=" << saTakerPaysNew << " node().saTakerGetsNew=" << saTakerGetsNew; resultCode = telFAILED_PROCESSING; break; } node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew); node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew); view().update (node().sleOffer); if (saOutPassAct == node().saTakerGets) { // Offer became unfunded. JLOG (j_.debug()) << "deliverNodeReverse: offer became unfunded."; node().bEntryAdvance = true; // XXX When don't we want to set advance? } else { assert (saOutPassAct < node().saTakerGets); } saOutAct += saOutPassAct; // Accumulate what is to be delivered from previous node. previousNode().saRevDeliver += saInPassAct; } if (saOutAct > saOutReq) { JLOG (j_.warn()) << "deliverNodeReverse: TOO MUCH:" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq; } assert(saOutAct <= saOutReq); if (resultCode == tesSUCCESS && !saOutAct) resultCode = tecPATH_DRY; // Unable to meet request, consider path dry. // Design invariant: if nothing was actually delivered, return tecPATH_DRY. JLOG (j_.trace()) << "deliverNodeReverse<" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq << " saPrvDlvReq=" << previousNode().saRevDeliver; return resultCode; }
// OPTIMIZE: When calculating path increment, note if increment consumes all // liquidity. No need to revisit path in the future if all liquidity is used. // TER PathCursor::advanceNode (bool const bReverse) const { TER resultCode = tesSUCCESS; // Taker is the active party against an offer in the ledger - the entity // that is taking advantage of an offer in the order book. WriteLog (lsTRACE, RippleCalc) << "advanceNode: TakerPays:" << node().saTakerPays << " TakerGets:" << node().saTakerGets; int loopCount = 0; do { // VFALCO NOTE Why not use a for() loop? // VFALCO TODO The limit on loop iterations puts an // upper limit on the number of different quality // levels (ratio of pay:get) that will be considered for one path. // Changing this value has repercusssions on validation and consensus. // if (++loopCount > NODE_ADVANCE_MAX_LOOPS) { WriteLog (lsWARNING, RippleCalc) << "Loop count exceeded"; return tefEXCEPTION; } bool bDirectDirDirty = node().directory.initialize ( { previousNode().issue_, node().issue_}, view()); if (auto advance = node().directory.advance (view())) { bDirectDirDirty = true; if (advance == NodeDirectory::NEW_QUALITY) { // We didn't run off the end of this order book and found // another quality directory. WriteLog (lsTRACE, RippleCalc) << "advanceNode: Quality advance: node.directory.current=" << node().directory.current; } else if (bReverse) { WriteLog (lsTRACE, RippleCalc) << "advanceNode: No more offers."; node().offerIndex_ = 0; break; } else { // No more offers. Should be done rather than fall off end of // book. WriteLog (lsWARNING, RippleCalc) << "advanceNode: Unreachable: " << "Fell off end of order book."; // FIXME: why? return telFAILED_PROCESSING; } } if (bDirectDirDirty) { // Our quality changed since last iteration. // Use the rate from the directory. node().saOfrRate = amountFromQuality ( getQuality (node().directory.current)); // For correct ratio node().uEntry = 0; node().bEntryAdvance = true; WriteLog (lsTRACE, RippleCalc) << "advanceNode: directory dirty: node.saOfrRate=" << node().saOfrRate; } if (!node().bEntryAdvance) { if (node().bFundsDirty) { // We were called again probably merely to update structure // variables. node().saTakerPays = node().sleOffer->getFieldAmount (sfTakerPays); node().saTakerGets = node().sleOffer->getFieldAmount (sfTakerGets); // Funds left. node().saOfferFunds = accountFunds(view(), node().offerOwnerAccount_, node().saTakerGets, fhZERO_IF_FROZEN, getConfig()); node().bFundsDirty = false; WriteLog (lsTRACE, RippleCalc) << "advanceNode: funds dirty: node().saOfrRate=" << node().saOfrRate; } else { WriteLog (lsTRACE, RippleCalc) << "advanceNode: as is"; } } else if (!dirNext (view(), node().directory.current, node().directory.ledgerEntry, node().uEntry, node().offerIndex_)) // This is the only place that offerIndex_ changes. { // Failed to find an entry in directory. // Do another cur directory iff multiQuality_ if (multiQuality_) { // We are allowed to process multiple qualities if this is the // only path. WriteLog (lsTRACE, RippleCalc) << "advanceNode: next quality"; node().directory.advanceNeeded = true; // Process next quality. } else if (!bReverse) { // We didn't run dry going backwards - why are we running dry // going forwards - this should be impossible! // TODO(tom): these warnings occur in production! They // shouldn't. WriteLog (lsWARNING, RippleCalc) << "advanceNode: unreachable: ran out of offers"; return telFAILED_PROCESSING; } else { // Ran off end of offers. node().bEntryAdvance = false; // Done. node().offerIndex_ = 0; // Report no more entries. } } else { // Got a new offer. node().sleOffer = view().peek (keylet::offer(node().offerIndex_)); if (!node().sleOffer) { // Corrupt directory that points to an entry that doesn't exist. // This has happened in production. WriteLog (lsWARNING, RippleCalc) << "Missing offer in directory"; node().bEntryAdvance = true; } else { node().offerOwnerAccount_ = node().sleOffer->getAccountID (sfAccount); node().saTakerPays = node().sleOffer->getFieldAmount (sfTakerPays); node().saTakerGets = node().sleOffer->getFieldAmount (sfTakerGets); AccountIssue const accountIssue ( node().offerOwnerAccount_, node().issue_); WriteLog (lsTRACE, RippleCalc) << "advanceNode: offerOwnerAccount_=" << to_string (node().offerOwnerAccount_) << " node.saTakerPays=" << node().saTakerPays << " node.saTakerGets=" << node().saTakerGets << " node.offerIndex_=" << node().offerIndex_; if (node().sleOffer->isFieldPresent (sfExpiration) && (node().sleOffer->getFieldU32 (sfExpiration) <= view().parentCloseTime())) { // Offer is expired. WriteLog (lsTRACE, RippleCalc) << "advanceNode: expired offer"; rippleCalc_.permanentlyUnfundedOffers_.insert( node().offerIndex_); continue; } if (node().saTakerPays <= zero || node().saTakerGets <= zero) { // Offer has bad amounts. Offers should never have a bad // amounts. auto const index = node().offerIndex_; if (bReverse) { // Past internal error, offer had bad amounts. // This has occurred in production. WriteLog (lsWARNING, RippleCalc) << "advanceNode: PAST INTERNAL ERROR" << " REVERSE: OFFER NON-POSITIVE:" << " node.saTakerPays=" << node().saTakerPays << " node.saTakerGets=" << node().saTakerGets; // Mark offer for always deletion. rippleCalc_.permanentlyUnfundedOffers_.insert ( node().offerIndex_); } else if (rippleCalc_.permanentlyUnfundedOffers_.find (index) != rippleCalc_.permanentlyUnfundedOffers_.end ()) { // Past internal error, offer was found failed to place // this in permanentlyUnfundedOffers_. // Just skip it. It will be deleted. WriteLog (lsDEBUG, RippleCalc) << "advanceNode: PAST INTERNAL ERROR " << " FORWARD CONFIRM: OFFER NON-POSITIVE:" << " node.saTakerPays=" << node().saTakerPays << " node.saTakerGets=" << node().saTakerGets; } else { // Reverse should have previously put bad offer in list. // An internal error previously left a bad offer. WriteLog (lsWARNING, RippleCalc) << "advanceNode: INTERNAL ERROR" <<" FORWARD NEWLY FOUND: OFFER NON-POSITIVE:" << " node.saTakerPays=" << node().saTakerPays << " node.saTakerGets=" << node().saTakerGets; // Don't process at all, things are in an unexpected // state for this transactions. resultCode = tefEXCEPTION; } continue; } // Allowed to access source from this node? // // XXX This can get called multiple times for same source in a // row, caching result would be nice. // // XXX Going forward could we fund something with a worse // quality which was previously skipped? Might need to check // quality. auto itForward = pathState_.forward().find (accountIssue); const bool bFoundForward = itForward != pathState_.forward().end (); // Only allow a source to be used once, in the first node // encountered from initial path scan. This prevents // conflicting uses of the same balance when going reverse vs // forward. if (bFoundForward && itForward->second != nodeIndex_ && node().offerOwnerAccount_ != node().issue_.account) { // Temporarily unfunded. Another node uses this source, // ignore in this offer. WriteLog (lsTRACE, RippleCalc) << "advanceNode: temporarily unfunded offer" << " (forward)"; continue; } // This is overly strict. For contributions to past. We should // only count source if actually used. auto itReverse = pathState_.reverse().find (accountIssue); bool bFoundReverse = itReverse != pathState_.reverse().end (); // For this quality increment, only allow a source to be used // from a single node, in the first node encountered from // applying offers in reverse. if (bFoundReverse && itReverse->second != nodeIndex_ && node().offerOwnerAccount_ != node().issue_.account) { // Temporarily unfunded. Another node uses this source, // ignore in this offer. WriteLog (lsTRACE, RippleCalc) << "advanceNode: temporarily unfunded offer" <<" (reverse)"; continue; } // Determine if used in past. // We only need to know if it might need to be marked unfunded. auto itPast = rippleCalc_.mumSource_.find (accountIssue); bool bFoundPast = (itPast != rippleCalc_.mumSource_.end ()); // Only the current node is allowed to use the source. node().saOfferFunds = accountFunds(view(), node().offerOwnerAccount_, node().saTakerGets, fhZERO_IF_FROZEN, getConfig()); // Funds held. if (node().saOfferFunds <= zero) { // Offer is unfunded. WriteLog (lsTRACE, RippleCalc) << "advanceNode: unfunded offer"; if (bReverse && !bFoundReverse && !bFoundPast) { // Never mentioned before, clearly just: found unfunded. // That is, even if this offer fails due to fill or kill // still do deletions. // Mark offer for always deletion. rippleCalc_.permanentlyUnfundedOffers_.insert (node().offerIndex_); } else { // Moving forward, don't need to insert again // Or, already found it. } // YYY Could verify offer is correct place for unfundeds. continue; } if (bReverse // Need to remember reverse mention. && !bFoundPast // Not mentioned in previous passes. && !bFoundReverse) // New to pass. { // Consider source mentioned by current path state. WriteLog (lsTRACE, RippleCalc) << "advanceNode: remember=" << node().offerOwnerAccount_ << "/" << node().issue_; pathState_.insertReverse (accountIssue, nodeIndex_); } node().bFundsDirty = false; node().bEntryAdvance = false; } } } while (resultCode == tesSUCCESS && (node().bEntryAdvance || node().directory.advanceNeeded)); if (resultCode == tesSUCCESS) { WriteLog (lsTRACE, RippleCalc) << "advanceNode: node.offerIndex_=" << node().offerIndex_; } else { WriteLog (lsDEBUG, RippleCalc) << "advanceNode: resultCode=" << transToken (resultCode); } return resultCode; }
// The reverse pass has been narrowing by credit available and inflating by fees // as it worked backwards. Now, for the current account node, take the actual // amount from previous and adjust forward balances. // // Perform balance adjustments between previous and current node. // - The previous node: specifies what to push through to current. // - All of previous output is consumed. // // Then, compute current node's output for next node. // - Current node: specify what to push through to next. // - Output to next node is computed as input minus quality or transfer fee. // - If next node is an offer and output is non-XRP then we are the issuer and // do not need to push funds. // - If next node is an offer and output is XRP then we need to deliver funds to // limbo. TER PathCursor::forwardLiquidityForAccount () const { TER resultCode = tesSUCCESS; auto const lastNodeIndex = pathState_.nodes().size () - 1; auto viewJ = rippleCalc_.logs_.journal ("View"); std::uint64_t uRateMax = 0; AccountID const& previousAccountID = previousNode().isAccount() ? previousNode().account_ : node().account_; // Offers are always issue. AccountID const& nextAccountID = nextNode().isAccount() ? nextNode().account_ : node().account_; auto const qualityIn = nodeIndex_ ? quality_in (view(), node().account_, previousAccountID, node().issue_.currency) : parityRate; auto const qualityOut = (nodeIndex_ == lastNodeIndex) ? quality_out (view(), node().account_, nextAccountID, node().issue_.currency) : parityRate; // When looking backward (prv) for req we care about what we just // calculated: use fwd. // When looking forward (cur) for req we care about what was desired: use // rev. // For nextNode().isAccount() auto saPrvRedeemAct = previousNode().saFwdRedeem.zeroed(); auto saPrvIssueAct = previousNode().saFwdIssue.zeroed(); // For !previousNode().isAccount() auto saPrvDeliverAct = previousNode().saFwdDeliver.zeroed (); JLOG (j_.trace()) << "forwardLiquidityForAccount> " << "nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex << " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem << " saPrvIssueReq:" << previousNode().saFwdIssue << " previousNode.saFwdDeliver:" << previousNode().saFwdDeliver << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " node.saRevDeliver:" << node().saRevDeliver; // Ripple through account. if (previousNode().isAccount() && nextNode().isAccount()) { // Next is an account, must be rippling. if (!nodeIndex_) { // ^ --> ACCOUNT --> account // For the first node, calculate amount to ripple based on what is // available. node().saFwdRedeem = node().saRevRedeem; if (pathState_.inReq() >= beast::zero) { // Limit by send max. node().saFwdRedeem = std::min ( node().saFwdRedeem, pathState_.inReq() - pathState_.inAct()); } pathState_.setInPass (node().saFwdRedeem); node().saFwdIssue = node().saFwdRedeem == node().saRevRedeem // Fully redeemed. ? node().saRevIssue : STAmount (node().saRevIssue); if (node().saFwdIssue && pathState_.inReq() >= beast::zero) { // Limit by send max. node().saFwdIssue = std::min ( node().saFwdIssue, pathState_.inReq() - pathState_.inAct() - node().saFwdRedeem); } pathState_.setInPass (pathState_.inPass() + node().saFwdIssue); JLOG (j_.trace()) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT --> account :" << " saInReq=" << pathState_.inReq() << " saInAct=" << pathState_.inAct() << " node.saFwdRedeem:" << node().saFwdRedeem << " node.saRevIssue:" << node().saRevIssue << " node.saFwdIssue:" << node().saFwdIssue << " pathState_.saInPass:"******"forwardLiquidityForAccount: account --> " << "ACCOUNT --> $ :" << " previousAccountID=" << to_string (previousAccountID) << " node.account_=" << to_string (node().account_) << " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem << " previousNode.saFwdIssue:" << previousNode().saFwdIssue; // Last node. Accept all funds. Calculate amount actually to credit. auto& saCurReceive = pathState_.outPass(); STAmount saIssueCrd = qualityIn >= parityRate ? previousNode().saFwdIssue // No fee. : multiplyRound ( previousNode().saFwdIssue, qualityIn, true); // Amount to credit. // Amount to credit. Credit for less than received as a surcharge. pathState_.setOutPass (previousNode().saFwdRedeem + saIssueCrd); if (saCurReceive) { // Actually receive. resultCode = rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ); } else { // After applying quality, total payment was microscopic. resultCode = tecPATH_DRY; } } else { // account --> ACCOUNT --> account JLOG (j_.trace()) << "forwardLiquidityForAccount: account --> " << "ACCOUNT --> account"; node().saFwdRedeem.clear (node().saRevRedeem); node().saFwdIssue.clear (node().saRevIssue); // Previous redeem part 1: redeem -> redeem if (previousNode().saFwdRedeem && node().saRevRedeem) // Previous wants to redeem. { // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, parityRate, qualityOut, previousNode().saFwdRedeem, node().saRevRedeem, saPrvRedeemAct, node().saFwdRedeem, uRateMax); } // Previous issue part 1: issue -> redeem if (previousNode().saFwdIssue != saPrvIssueAct // Previous wants to issue. && node().saRevRedeem != node().saFwdRedeem) // Current has more to redeem to next. { // Rate: quality in : quality out rippleLiquidity ( rippleCalc_, qualityIn, qualityOut, previousNode().saFwdIssue, node().saRevRedeem, saPrvIssueAct, node().saFwdRedeem, uRateMax); } // Previous redeem part 2: redeem -> issue. if (previousNode().saFwdRedeem != saPrvRedeemAct // Previous still wants to redeem. && node().saRevRedeem == node().saFwdRedeem // Current redeeming is done can issue. && node().saRevIssue) // Current wants to issue. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdRedeem, node().saRevIssue, saPrvRedeemAct, node().saFwdIssue, uRateMax); } // Previous issue part 2 : issue -> issue if (previousNode().saFwdIssue != saPrvIssueAct // Previous wants to issue. && node().saRevRedeem == node().saFwdRedeem // Current redeeming is done can issue. && node().saRevIssue) // Current wants to issue. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, previousNode().saFwdIssue, node().saRevIssue, saPrvIssueAct, node().saFwdIssue, uRateMax); } STAmount saProvide = node().saFwdRedeem + node().saFwdIssue; // Adjust prv --> cur balance : take all inbound resultCode = saProvide ? rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ) : tecPATH_DRY; } } else if (previousNode().isAccount() && !nextNode().isAccount()) { // Current account is issuer to next offer. // Determine deliver to offer amount. // Don't adjust outbound balances- keep funds with issuer as limbo. // If issuer hold's an offer owners inbound IOUs, there is no fee and // redeem/issue will transparently happen. if (nodeIndex_) { // Non-XRP, current node is the issuer. JLOG (j_.trace()) << "forwardLiquidityForAccount: account --> " << "ACCOUNT --> offer"; node().saFwdDeliver.clear (node().saRevDeliver); // redeem -> issue/deliver. // Previous wants to redeem. // Current is issuing to an offer so leave funds in account as // "limbo". if (previousNode().saFwdRedeem) // Previous wants to redeem. { // Rate : 1.0 : transfer_rate // XXX Is having the transfer rate here correct? rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdRedeem, node().saRevDeliver, saPrvRedeemAct, node().saFwdDeliver, uRateMax); } // issue -> issue/deliver if (previousNode().saFwdRedeem == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. && previousNode().saFwdIssue) // Previous wants to issue. To next must be ok. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, previousNode().saFwdIssue, node().saRevDeliver, saPrvIssueAct, node().saFwdDeliver, uRateMax); } // Adjust prv --> cur balance : take all inbound resultCode = node().saFwdDeliver ? rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ) : tecPATH_DRY; // Didn't actually deliver anything. } else { // Delivering amount requested from downstream. node().saFwdDeliver = node().saRevDeliver; // If limited, then limit by send max and available. if (pathState_.inReq() >= beast::zero) { // Limit by send max. node().saFwdDeliver = std::min ( node().saFwdDeliver, pathState_.inReq() - pathState_.inAct()); // Limit XRP by available. No limit for non-XRP as issuer. if (isXRP (node().issue_)) node().saFwdDeliver = std::min ( node().saFwdDeliver, accountHolds(view(), node().account_, xrpCurrency(), xrpAccount(), fhIGNORE_FREEZE, viewJ)); // XRP can't be frozen } // Record amount sent for pass. pathState_.setInPass (node().saFwdDeliver); if (!node().saFwdDeliver) { resultCode = tecPATH_DRY; } else if (!isXRP (node().issue_)) { // Non-XRP, current node is the issuer. // We could be delivering to multiple accounts, so we don't know // which ripple balance will be adjusted. Assume just issuing. JLOG (j_.trace()) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT -- !XRP --> offer"; // As the issuer, would only issue. // Don't need to actually deliver. As from delivering leave in // the issuer as limbo. } else { JLOG (j_.trace()) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT -- XRP --> offer"; // Deliver XRP to limbo. resultCode = accountSend(view(), node().account_, xrpAccount(), node().saFwdDeliver, viewJ); } } } else if (!previousNode().isAccount() && nextNode().isAccount()) { if (nodeIndex_ == lastNodeIndex) { // offer --> ACCOUNT --> $ JLOG (j_.trace()) << "forwardLiquidityForAccount: offer --> " << "ACCOUNT --> $ : " << previousNode().saFwdDeliver; // Amount to credit. pathState_.setOutPass (previousNode().saFwdDeliver); // No income balance adjustments necessary. The paying side inside // the offer paid to this account. } else { // offer --> ACCOUNT --> account JLOG (j_.trace()) << "forwardLiquidityForAccount: offer --> " << "ACCOUNT --> account"; node().saFwdRedeem.clear (node().saRevRedeem); node().saFwdIssue.clear (node().saRevIssue); // deliver -> redeem if (previousNode().saFwdDeliver && node().saRevRedeem) // Previous wants to deliver and can current redeem. { // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, parityRate, qualityOut, previousNode().saFwdDeliver, node().saRevRedeem, saPrvDeliverAct, node().saFwdRedeem, uRateMax); } // deliver -> issue // Wants to redeem and current would and can issue. if (previousNode().saFwdDeliver != saPrvDeliverAct // Previous still wants to deliver. && node().saRevRedeem == node().saFwdRedeem // Current has more to redeem to next. && node().saRevIssue) // Current wants issue. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdDeliver, node().saRevIssue, saPrvDeliverAct, node().saFwdIssue, uRateMax); } // No income balance adjustments necessary. The paying side inside // the offer paid and the next link will receive. STAmount saProvide = node().saFwdRedeem + node().saFwdIssue; if (!saProvide) resultCode = tecPATH_DRY; } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. JLOG (j_.trace()) << "forwardLiquidityForAccount: offer --> ACCOUNT --> offer"; node().saFwdDeliver.clear (node().saRevDeliver); if (previousNode().saFwdDeliver && node().saRevDeliver) { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdDeliver, node().saRevDeliver, saPrvDeliverAct, node().saFwdDeliver, uRateMax); } // No income balance adjustments necessary. The paying side inside the // offer paid and the next link will receive. if (!node().saFwdDeliver) resultCode = tecPATH_DRY; } return resultCode; }
// To deliver from an order book, when computing TER PathCursor::deliverNodeReverse ( Account const& uOutAccountID, // --> Output owner's account. STAmount const& saOutReq, // --> Funds requested to be // delivered for an increment. STAmount& saOutAct) const // <-- Funds actually delivered for an // increment. { TER resultCode = tesSUCCESS; node().directory.restart(multiQuality_); // Accumulation of what the previous node must deliver. // Possible optimization: Note this gets zeroed on each increment, ideally // only on first increment, then it could be a limit on the forward pass. saOutAct.clear (saOutReq); WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse>" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq << " saPrvDlvReq=" << previousNode().saRevDeliver; assert (saOutReq != zero); int loopCount = 0; // While we did not deliver as much as requested: while (saOutAct < saOutReq) { if (++loopCount > CALC_NODE_DELIVER_MAX_LOOPS) { WriteLog (lsFATAL, RippleCalc) << "loop count exceeded"; return telFAILED_PROCESSING; } resultCode = advanceNode (saOutAct, true); // If needed, advance to next funded offer. if (resultCode != tesSUCCESS || !node().offerIndex_) // Error or out of offers. break; auto const hasFee = node().offerOwnerAccount_ == node().issue_.account || uOutAccountID == node().issue_.account; // Issuer sending or receiving. const STAmount saOutFeeRate = hasFee ? saOne // No fee. : node().transferRate_; // Transfer rate of issuer. WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse:" << " offerOwnerAccount_=" << node().offerOwnerAccount_ << " uOutAccountID=" << uOutAccountID << " node().issue_.account=" << node().issue_.account << " node().transferRate_=" << node().transferRate_ << " saOutFeeRate=" << saOutFeeRate; if (multiQuality_) { // In multi-quality mode, ignore rate. } else if (!node().saRateMax) { // Set initial rate. node().saRateMax = saOutFeeRate; WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse: Set initial rate:" << " node().saRateMax=" << node().saRateMax << " saOutFeeRate=" << saOutFeeRate; } else if (saOutFeeRate > node().saRateMax) { // Offer exceeds initial rate. WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse: Offer exceeds initial rate:" << " node().saRateMax=" << node().saRateMax << " saOutFeeRate=" << saOutFeeRate; break; // Done. Don't bother looking for smaller transferRates. } else if (saOutFeeRate < node().saRateMax) { // Reducing rate. Additional offers will only considered for this // increment if they are at least this good. // // At this point, the overall rate is reducing, while the overall // rate is not saOutFeeRate, it would be wrong to add anything with // a rate above saOutFeeRate. // // The rate would be reduced if the current offer was from the // issuer and the previous offer wasn't. node().saRateMax = saOutFeeRate; WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse: Reducing rate:" << " node().saRateMax=" << node().saRateMax; } // Amount that goes to the taker. STAmount saOutPassReq = std::min ( std::min (node().saOfferFunds, node().saTakerGets), saOutReq - saOutAct); // Maximum out - assuming no out fees. STAmount saOutPassAct = saOutPassReq; // Amount charged to the offer owner. // // The fee goes to issuer. The fee is paid by offer owner and not passed // as a cost to taker. // // Round down: prefer liquidity rather than microscopic fees. STAmount saOutPlusFees = STAmount::mulRound ( saOutPassAct, saOutFeeRate, false); // Offer out with fees. WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse:" << " saOutReq=" << saOutReq << " saOutAct=" << saOutAct << " node().saTakerGets=" << node().saTakerGets << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees << " node().saOfferFunds=" << node().saOfferFunds; if (saOutPlusFees > node().saOfferFunds) { // Offer owner can not cover all fees, compute saOutPassAct based on // node().saOfferFunds. saOutPlusFees = node().saOfferFunds; // Round up: prefer liquidity rather than microscopic fees. But, // limit by requested. auto fee = STAmount::divRound (saOutPlusFees, saOutFeeRate, true); saOutPassAct = std::min (saOutPassReq, fee); WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse: Total exceeds fees:" << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees << " node().saOfferFunds=" << node().saOfferFunds; } // Compute portion of input needed to cover actual output. auto outputFee = STAmount::mulRound ( saOutPassAct, node().saOfrRate, node().saTakerPays, true); STAmount saInPassReq = std::min (node().saTakerPays, outputFee); STAmount saInPassAct; WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse:" << " outputFee=" << outputFee << " saInPassReq=" << saInPassReq << " node().saOfrRate=" << node().saOfrRate << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees; if (!saInPassReq) // FIXME: This is bogus { // After rounding did not want anything. WriteLog (lsDEBUG, RippleCalc) << "deliverNodeReverse: micro offer is unfunded."; node().bEntryAdvance = true; continue; } // Find out input amount actually available at current rate. else if (!isXRP(previousNode().account_)) { // account --> OFFER --> ? // Due to node expansion, previous is guaranteed to be the issuer. // // Previous is the issuer and receiver is an offer, so no fee or // quality. // // Previous is the issuer and has unlimited funds. // // Offer owner is obtaining IOUs via an offer, so credit line limits // are ignored. As limits are ignored, don't need to adjust // previous account's balance. saInPassAct = saInPassReq; WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse: account --> OFFER --> ? :" << " saInPassAct=" << saInPassAct; } else { // offer --> OFFER --> ? // Compute in previous offer node how much could come in. // TODO(tom): Fix nasty recursion here! resultCode = increment(-1).deliverNodeReverse( node().offerOwnerAccount_, saInPassReq, saInPassAct); WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse: offer --> OFFER --> ? :" << " saInPassAct=" << saInPassAct; } if (resultCode != tesSUCCESS) break; if (saInPassAct < saInPassReq) { // Adjust output to conform to limited input. auto outputRequirements = STAmount::divRound ( saInPassAct, node().saOfrRate, node().saTakerGets, true); saOutPassAct = std::min (saOutPassReq, outputRequirements); auto outputFees = STAmount::mulRound ( saOutPassAct, saOutFeeRate, true); saOutPlusFees = std::min (node().saOfferFunds, outputFees); WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse: adjusted:" << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees; } else { // TODO(tom): more logging here. assert (saInPassAct == saInPassReq); } // Funds were spent. node().bFundsDirty = true; // Want to deduct output to limit calculations while computing reverse. // Don't actually need to send. // // Sending could be complicated: could fund a previous offer not yet // visited. However, these deductions and adjustments are tenative. // // Must reset balances when going forward to perform actual transfers. resultCode = ledger().accountSend ( node().offerOwnerAccount_, node().issue_.account, saOutPassAct); if (resultCode != tesSUCCESS) break; // Adjust offer STAmount saTakerGetsNew = node().saTakerGets - saOutPassAct; STAmount saTakerPaysNew = node().saTakerPays - saInPassAct; if (saTakerPaysNew < zero || saTakerGetsNew < zero) { WriteLog (lsWARNING, RippleCalc) << "deliverNodeReverse: NEGATIVE:" << " node().saTakerPaysNew=" << saTakerPaysNew << " node().saTakerGetsNew=%s" << saTakerGetsNew; resultCode = telFAILED_PROCESSING; break; } node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew); node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew); ledger().entryModify (node().sleOffer); if (saOutPassAct == node().saTakerGets) { // Offer became unfunded. WriteLog (lsDEBUG, RippleCalc) << "deliverNodeReverse: offer became unfunded."; node().bEntryAdvance = true; // XXX When don't we want to set advance? } else { assert (saOutPassAct < node().saTakerGets); } saOutAct += saOutPassAct; // Accumulate what is to be delivered from previous node. previousNode().saRevDeliver += saInPassAct; } CondLog (saOutAct > saOutReq, lsWARNING, RippleCalc) << "deliverNodeReverse: TOO MUCH:" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq; assert (saOutAct <= saOutReq); if (resultCode == tesSUCCESS && !saOutAct) resultCode = tecPATH_DRY; // Unable to meet request, consider path dry. // Design invariant: if nothing was actually delivered, return tecPATH_DRY. WriteLog (lsTRACE, RippleCalc) << "deliverNodeReverse<" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq << " saPrvDlvReq=" << previousNode().saRevDeliver; return resultCode; }
TER PathCursor::reverseLiquidityForAccount () const { TER terResult = tesSUCCESS; auto const lastNodeIndex = nodeSize () - 1; auto const isFinalNode = (nodeIndex_ == lastNodeIndex); // 0 quality means none has yet been determined. std::uint64_t uRateMax = 0; // Current is allowed to redeem to next. const bool previousNodeIsAccount = !nodeIndex_ || previousNode().isAccount(); const bool nextNodeIsAccount = isFinalNode || nextNode().isAccount(); AccountID const& previousAccountID = previousNodeIsAccount ? previousNode().account_ : node().account_; AccountID const& nextAccountID = nextNodeIsAccount ? nextNode().account_ : node().account_; // Offers are always issue. // This is the quality from from the previous node to this one. auto const qualityIn = (nodeIndex_ != 0) ? quality_in (view(), node().account_, previousAccountID, node().issue_.currency) : parityRate; // And this is the quality from the next one to this one. auto const qualityOut = (nodeIndex_ != lastNodeIndex) ? quality_out (view(), node().account_, nextAccountID, node().issue_.currency) : parityRate; // For previousNodeIsAccount: // Previous account is already owed. const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex_ != 0) ? creditBalance (view(), node().account_, previousAccountID, node().issue_.currency) : STAmount (node().issue_); // The limit amount that the previous account may owe. const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex_ != 0) ? creditLimit (view(), node().account_, previousAccountID, node().issue_.currency) : STAmount (node().issue_); // Next account is owed. const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex_ != lastNodeIndex) ? creditBalance (view(), node().account_, nextAccountID, node().issue_.currency) : STAmount (node().issue_); JLOG (j_.trace()) << "reverseLiquidityForAccount>" << " nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex << " previousAccountID=" << previousAccountID << " node.account_=" << node().account_ << " nextAccountID=" << nextAccountID << " currency=" << node().issue_.currency << " qualityIn=" << qualityIn << " qualityOut=" << qualityOut << " saPrvOwed=" << saPrvOwed << " saPrvLimit=" << saPrvLimit; // Requests are computed to be the maximum flow possible. // Previous can redeem the owed IOUs it holds. const STAmount saPrvRedeemReq = (saPrvOwed > beast::zero) ? saPrvOwed : STAmount (saPrvOwed.issue ()); // Previous can issue up to limit minus whatever portion of limit already // used (not including redeemable amount) - another "maximum flow". const STAmount saPrvIssueReq = (saPrvOwed < beast::zero) ? saPrvLimit + saPrvOwed : saPrvLimit; // Precompute these values in case we have an order book. auto deliverCurrency = previousNode().saRevDeliver.getCurrency (); const STAmount saPrvDeliverReq ( {deliverCurrency, previousNode().saRevDeliver.getIssuer ()}, -1); // -1 means unlimited delivery. // Set to zero, because we're trying to hit the previous node. auto saCurRedeemAct = node().saRevRedeem.zeroed(); // Track the amount we actually redeem. auto saCurIssueAct = node().saRevIssue.zeroed(); // For !nextNodeIsAccount auto saCurDeliverAct = node().saRevDeliver.zeroed(); JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " saPrvRedeemReq:" << saPrvRedeemReq << " saPrvIssueReq:" << saPrvIssueReq << " previousNode.saRevDeliver:" << previousNode().saRevDeliver << " saPrvDeliverReq:" << saPrvDeliverReq << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " saNxtOwed:" << saNxtOwed; // VFALCO-FIXME this generates errors //JLOG (j_.trace()) << pathState_.getJson (); // Current redeem req can't be more than IOUs on hand. assert (!node().saRevRedeem || -saNxtOwed >= node().saRevRedeem); assert (!node().saRevIssue // If not issuing, fine. || saNxtOwed >= beast::zero // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: // Sender holding next IOUs. || -saNxtOwed == node().saRevRedeem); // If issue req, then redeem req must consume all owed. if (nodeIndex_ == 0) { // ^ --> ACCOUNT --> account|offer // Nothing to do, there is no previous to adjust. // // TODO(tom): we could have skipped all that setup and just left // or even just never call this whole routine on nodeIndex_ = 0! } // The next four cases correspond to the table at the bottom of this Wiki // page section: https://ripple.com/wiki/Transit_Fees#Implementation else if (previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // account --> ACCOUNT --> $ // Overall deliverable. const STAmount saCurWantedReq = std::min ( pathState_.outReq() - pathState_.outAct(), saPrvLimit + saPrvOwed); auto saCurWantedAct = saCurWantedReq.zeroed (); JLOG (j_.trace()) << "reverseLiquidityForAccount: account --> " << "ACCOUNT --> $ :" << " saCurWantedReq=" << saCurWantedReq; // Calculate redeem if (saPrvRedeemReq) // Previous has IOUs to redeem. { // Redeem your own IOUs at 1:1 saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq); previousNode().saRevRedeem = saCurWantedAct; uRateMax = STAmount::uRateOne; JLOG (j_.trace()) << "reverseLiquidityForAccount: Redeem at 1:1" << " saPrvRedeemReq=" << saPrvRedeemReq << " (available) previousNode.saRevRedeem=" << previousNode().saRevRedeem << " uRateMax=" << amountFromQuality (uRateMax).getText (); } else { previousNode().saRevRedeem.clear (saPrvRedeemReq); } // Calculate issuing. previousNode().saRevIssue.clear (saPrvIssueReq); if (saCurWantedReq != saCurWantedAct // Need more. && saPrvIssueReq) // Will accept IOUs from previous. { // Rate: quality in : 1.0 // If we previously redeemed and this has a poorer rate, this // won't be included the current increment. rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, saCurWantedReq, previousNode().saRevIssue, saCurWantedAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: Issuing: Rate: " << "quality in : 1.0" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurWantedAct:" << saCurWantedAct; } if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } } else { // Not final node. // account --> ACCOUNT --> account previousNode().saRevRedeem.clear (saPrvRedeemReq); previousNode().saRevIssue.clear (saPrvIssueReq); // redeem (part 1) -> redeem if (node().saRevRedeem // Next wants IOUs redeemed from current account. && saPrvRedeemReq) // Previous has IOUs to redeem to the current account. { // TODO(tom): add English. // Rate : 1.0 : quality out - we must accept our own IOUs // as 1:1. rippleLiquidity ( rippleCalc_, parityRate, qualityOut, saPrvRedeemReq, node().saRevRedeem, previousNode().saRevRedeem, saCurRedeemAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate : 1.0 : quality out" << " previousNode.saRevRedeem:" << previousNode().saRevRedeem << " saCurRedeemAct:" << saCurRedeemAct; } // issue (part 1) -> redeem if (node().saRevRedeem != saCurRedeemAct // The current node has more IOUs to redeem. && previousNode().saRevRedeem == saPrvRedeemReq) // The previous node has no IOUs to redeem remaining, so issues. { // Rate: quality in : quality out rippleLiquidity ( rippleCalc_, qualityIn, qualityOut, saPrvIssueReq, node().saRevRedeem, previousNode().saRevIssue, saCurRedeemAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate: quality in : quality out:" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurRedeemAct:" << saCurRedeemAct; } // redeem (part 2) -> issue. if (node().saRevIssue // Next wants IOUs issued. // TODO(tom): this condition seems redundant. && saCurRedeemAct == node().saRevRedeem // Can only issue if completed redeeming. && previousNode().saRevRedeem != saPrvRedeemReq) // Did not complete redeeming previous IOUs. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvRedeemReq, node().saRevIssue, previousNode().saRevRedeem, saCurIssueAct, uRateMax); JLOG (j_.debug()) << "reverseLiquidityForAccount: " << "Rate : 1.0 : transfer_rate:" << " previousNode.saRevRedeem:" << previousNode().saRevRedeem << " saCurIssueAct:" << saCurIssueAct; } // issue (part 2) -> issue if (node().saRevIssue != saCurIssueAct // Need wants more IOUs issued. && saCurRedeemAct == node().saRevRedeem // Can only issue if completed redeeming. && saPrvRedeemReq == previousNode().saRevRedeem // Previously redeemed all owed IOUs. && saPrvIssueReq) // Previous can issue. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, node().saRevIssue, previousNode().saRevIssue, saCurIssueAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate: quality in : 1.0:" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurIssueAct:" << saCurIssueAct; } if (!saCurRedeemAct && !saCurIssueAct) { // Did not make progress. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "^|account --> ACCOUNT --> account :" << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " saPrvOwed:" << saPrvOwed << " saCurRedeemAct:" << saCurRedeemAct << " saCurIssueAct:" << saCurIssueAct; } } else if (previousNodeIsAccount && !nextNodeIsAccount) { // account --> ACCOUNT --> offer // Note: deliver is always issue as ACCOUNT is the issuer for the offer // input. JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "account --> ACCOUNT --> offer"; previousNode().saRevRedeem.clear (saPrvRedeemReq); previousNode().saRevIssue.clear (saPrvIssueReq); // We have three cases: the nxt offer can be owned by current account, // previous account or some third party account. // // Also, the current account may or may not have a redeemable balance // with the account for the next offer, so we don't yet know if we're // redeeming or issuing. // // TODO(tom): Make sure deliver was cleared, or check actual is zero. // redeem -> deliver/issue. if (saPrvOwed > beast::zero // Previous has IOUs to redeem. && node().saRevDeliver) // Need some issued. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvRedeemReq, node().saRevDeliver, previousNode().saRevRedeem, saCurDeliverAct, uRateMax); } // issue -> deliver/issue if (saPrvRedeemReq == previousNode().saRevRedeem // Previously redeemed all owed. && node().saRevDeliver != saCurDeliverAct) // Still need some issued. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, node().saRevDeliver, previousNode().saRevIssue, saCurDeliverAct, uRateMax); } if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount: " << " node.saRevDeliver:" << node().saRevDeliver << " saCurDeliverAct:" << saCurDeliverAct << " saPrvOwed:" << saPrvOwed; } else if (!previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // offer --> ACCOUNT --> $ // Previous is an offer, no limit: redeem own IOUs. // // This is the final node; we can't look to the right to get values; // we have to go up to get the out value for the entire path state. STAmount const& saCurWantedReq = pathState_.outReq() - pathState_.outAct(); STAmount saCurWantedAct = saCurWantedReq.zeroed(); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "offer --> ACCOUNT --> $ :" << " saCurWantedReq:" << saCurWantedReq << " saOutAct:" << pathState_.outAct() << " saOutReq:" << pathState_.outReq(); if (saCurWantedReq <= beast::zero) { assert(false); JLOG (j_.fatal()) << "CurWantReq was not positive"; return tefEXCEPTION; } // The previous node is an offer; we are receiving our own currency. // The previous order book's entries might hold our issuances; might // not hold our issuances; might be our own offer. // // Assume the worst case, the case which costs the most to go // through, which is that it is not our own offer or our own // issuances. Later on the forward pass we may be able to do // better. // // TODO: this comment applies generally to this section - move it up // to a document. // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvDeliverReq, saCurWantedReq, previousNode().saRevDeliver, saCurWantedAct, uRateMax); if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " previousNode().saRevDeliver:" << previousNode().saRevDeliver << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurWantedAct:" << saCurWantedAct << " saCurWantedReq:" << saCurWantedReq; } else { // offer --> ACCOUNT --> account // Note: offer is always delivering(redeeming) as account is issuer. JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "offer --> ACCOUNT --> account :" << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue; // deliver -> redeem // TODO(tom): now we have more checking in nodeRipple, these checks // might be redundant. if (node().saRevRedeem) // Next wants us to redeem. { // cur holds IOUs from the account to the right, the nxt // account. If someone is making the current account get rid of // the nxt account's IOUs, then charge the input for quality // out. // // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, parityRate, qualityOut, saPrvDeliverReq, node().saRevRedeem, previousNode().saRevDeliver, saCurRedeemAct, uRateMax); } // deliver -> issue. if (node().saRevRedeem == saCurRedeemAct // Can only issue if previously redeemed all. && node().saRevIssue) // Need some issued. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvDeliverReq, node().saRevIssue, previousNode().saRevDeliver, saCurIssueAct, uRateMax); } JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " saCurRedeemAct:" << saCurRedeemAct << " node.saRevRedeem:" << node().saRevRedeem << " previousNode.saRevDeliver:" << previousNode().saRevDeliver << " node.saRevIssue:" << node().saRevIssue; if (!previousNode().saRevDeliver) { // Must want something. terResult = tecPATH_DRY; } } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. JLOG (j_.trace()) << "reverseLiquidityForAccount: offer --> ACCOUNT --> offer"; // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvDeliverReq, node().saRevDeliver, previousNode().saRevDeliver, saCurDeliverAct, uRateMax); if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } return terResult; }