Exemplo n.º 1
0
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);
}
Exemplo n.º 2
0
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;
}
Exemplo n.º 3
0
// 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;
}
Exemplo n.º 4
0
STAmount
Taker::get_funds (AccountID const& account, STAmount const& amount) const
{
    return accountFunds(view_, account, amount, fhZERO_IF_FROZEN, journal_);
}