static STAmount accountFundsHelper (ReadView const& view, AccountID const& id, STAmount const& saDefault, Issue const&, FreezeHandling freezeHandling, beast::Journal j) { return accountFunds (view, id, saDefault, freezeHandling, j); }
bool OfferStream::step (Logs& l) { // Modifying the order or logic of these // operations causes a protocol breaking change. auto viewJ = l.journal ("View"); for(;;) { // BookTip::step deletes the current offer from the view before // advancing to the next (unless the ledger entry is missing). if (! tip_.step(l)) return false; std::shared_ptr<SLE> entry = tip_.entry(); // If we exceed the maximum number of allowed steps, we're done. if (!counter_.step ()) return false; // Remove if missing if (! entry) { erase (view_); erase (cancelView_); continue; } // Remove if expired using d = NetClock::duration; using tp = NetClock::time_point; if (entry->isFieldPresent (sfExpiration) && tp{d{(*entry)[sfExpiration]}} <= expire_) { JLOG(j_.trace) << "Removing expired offer " << entry->getIndex(); offerDelete (cancelView_, cancelView_.peek(keylet::offer(entry->key())), viewJ); continue; } offer_ = Offer (entry, tip_.quality()); Amounts const amount (offer_.amount()); // Remove if either amount is zero if (amount.empty()) { JLOG(j_.warning) << "Removing bad offer " << entry->getIndex(); offerDelete (cancelView_, cancelView_.peek(keylet::offer(entry->key())), viewJ); offer_ = Offer{}; continue; } // Calculate owner funds auto const owner_funds = accountFunds(view_, offer_.owner(), amount.out, fhZERO_IF_FROZEN, viewJ); // Check for unfunded offer if (owner_funds <= zero) { // If the owner's balance in the pristine view is the same, // we haven't modified the balance and therefore the // offer is "found unfunded" versus "became unfunded" auto const original_funds = accountFunds(cancelView_, offer_.owner(), amount.out, fhZERO_IF_FROZEN, viewJ); if (original_funds == owner_funds) { offerDelete (cancelView_, cancelView_.peek( keylet::offer(entry->key())), viewJ); JLOG(j_.trace) << "Removing unfunded offer " << entry->getIndex(); } else { JLOG(j_.trace) << "Removing became unfunded offer " << entry->getIndex(); } offer_ = Offer{}; continue; } break; } return true; }
// 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; }
STAmount Taker::get_funds (AccountID const& account, STAmount const& amount) const { return accountFunds(view_, account, amount, fhZERO_IF_FROZEN, journal_); }