Beispiel #1
0
PreclaimResult
preclaim (PreflightResult const& preflightResult,
    Application& app, OpenView const& view)
{
    boost::optional<PreclaimContext const> ctx;
    if (preflightResult.rules != view.rules())
    {
        auto secondFlight = preflight(app, view.rules(),
            preflightResult.tx, preflightResult.flags,
                preflightResult.j);
        ctx.emplace(app, view, secondFlight.ter, secondFlight.tx,
            secondFlight.flags, secondFlight.j);
    }
    else
    {
        ctx.emplace(
            app, view, preflightResult.ter, preflightResult.tx,
                preflightResult.flags, preflightResult.j);
    }
    try
    {
        if (ctx->preflightResult != tesSUCCESS)
            return { *ctx, ctx->preflightResult, 0 };
        return{ *ctx, invoke_preclaim(*ctx) };
    }
    catch (std::exception const& e)
    {
        JLOG(ctx->j.fatal) <<
            "apply: " << e.what();
        return{ *ctx, tefEXCEPTION, 0 };
    }
}
std::pair<TER, bool>
doApply(PreclaimResult const& preclaimResult,
    Application& app, OpenView& view)
{
    if (preclaimResult.view.seq() != view.seq())
    {
        // Logic error from the caller. Don't have enough
        // info to recover.
        return{ tefEXCEPTION, false };
    }
    try
    {
        if (!preclaimResult.likelyToClaimFee)
            return{ preclaimResult.ter, false };
        ApplyContext ctx(app, view,
            preclaimResult.tx, preclaimResult.ter,
                preclaimResult.baseFee, preclaimResult.flags,
                    preclaimResult.j);
        if (view.txExists (ctx.tx.getTransactionID ()))
        {
            JLOG (preclaimResult.j.warning) << "transaction duplicates!";
            return {tefALREADY, false};
        }
        return invoke_apply(ctx);
    }
    catch (std::exception const& e)
    {
        JLOG(preclaimResult.j.fatal) <<
            "apply: " << e.what();
        return { tefEXCEPTION, false };
    }
}
Beispiel #3
0
std::pair<TER, bool>
apply (Application& app, OpenView& view,
    STTx const& tx, ApplyFlags flags,
        beast::Journal j)
{
    STAmountSO saved(view.info().parentCloseTime);
    auto pfresult = preflight(app, view.rules(), tx, flags, j);
    auto pcresult = preclaim(pfresult, app, view);
    return doApply(pcresult, app, view);
}
Beispiel #4
0
void
TxQ::processValidatedLedger(Application& app,
    OpenView const& view, bool timeLeap)
{
    auto const allowEscalation =
        (view.rules().enabled(featureFeeEscalation,
            app.config().features));
    if (!allowEscalation)
    {
        return;
    }

    feeMetrics_.updateFeeMetrics(app, view, timeLeap);

    auto ledgerSeq = view.info().seq;

    std::lock_guard<std::mutex> lock(mutex_);

    if (!timeLeap)
        maxSize_ = feeMetrics_.getTxnsExpected() * setup_.ledgersInQueue;

    // Remove any queued candidates whose LastLedgerSequence has gone by.
    // Stop if we leave maxSize_ candidates.
    size_t keptCandidates = 0;
    auto candidateIter = byFee_.begin();
    while (candidateIter != byFee_.end()
        && (!maxSize_ || keptCandidates < *maxSize_))
    {
        if (candidateIter->lastValid
            && *candidateIter->lastValid <= ledgerSeq)
        {
            candidateIter = erase(candidateIter);
        }
        else
        {
            ++keptCandidates;
            ++candidateIter;
        }
    }
    // Erase any candidates more than maxSize_.
    // This can help keep the queue from getting overfull.
    for (; candidateIter != byFee_.end(); ++candidateIter)
        candidateIter = erase(candidateIter);

    // Remove any TxQAccounts that don't have candidates
    // under them
    for (auto txQAccountIter = byAccount_.begin();
        txQAccountIter != byAccount_.end();)
    {
        if (txQAccountIter->second.empty())
            txQAccountIter = byAccount_.erase(txQAccountIter);
        else
            ++txQAccountIter;
    }
}
Beispiel #5
0
std::uint64_t
FeeMetrics::scaleFeeLevel(OpenView const& view) const
{
    auto fee = baseLevel;

    // Transactions in the open ledger so far
    auto const current = view.txCount();

    std::size_t target;
    std::uint32_t multiplier;
    {
        std::lock_guard <std::mutex> sl(lock_);

        // Target number of transactions allowed
        target = txnsExpected_;
        multiplier = escalationMultiplier_;
    }

    // Once the open ledger bypasses the target,
    // escalate the fee quickly.
    if (current > target)
    {
        // Compute escalated fee level
        // Don't care about the overflow flag
        fee = mulDiv(fee, current * current *
            multiplier, target * target).second;
    }

    return fee;
}
Beispiel #6
0
XRPAmount
TxQ::openLedgerFee(OpenView const& view) const
{
    auto metrics = getMetrics(view);

    // Don't care about the overflow flag
    return mulDiv(metrics.expFeeLevel,
        view.fees().base, metrics.referenceFeeLevel).second + 1;
}
Beispiel #7
0
TxQ::Metrics
TxQ::getMetrics(OpenView const& view) const
{
    Metrics result;

    std::lock_guard<std::mutex> lock(mutex_);

    result.txCount = byFee_.size();
    result.txQMaxSize = maxSize_;
    result.txInLedger = view.txCount();
    result.txPerLedger = feeMetrics_.getTxnsExpected();
    result.referenceFeeLevel = feeMetrics_.baseLevel;
    result.minFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 :
        feeMetrics_.baseLevel;
    result.medFeeLevel = feeMetrics_.getEscalationMultiplier();
    result.expFeeLevel = feeMetrics_.scaleFeeLevel(view);

    return result;
}
Beispiel #8
0
bool
TxQ::accept(Application& app,
    OpenView& view)
{
    auto const allowEscalation =
        (view.rules().enabled(featureFeeEscalation,
            app.config().features));
    if (!allowEscalation)
    {
        return false;
    }

    /* Move transactions from the queue from largest fee to smallest.
       As we add more transactions, the required fee will increase.
       Stop when the transaction fee gets lower than the required fee.
    */

    auto ledgerChanged = false;

    std::lock_guard<std::mutex> lock(mutex_);

    for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
    {
        auto const requiredFeeLevel = feeMetrics_.scaleFeeLevel(view);
        auto const feeLevelPaid = candidateIter->feeLevel;
        JLOG(j_.trace) << "Queued transaction " <<
            candidateIter->txID << " from account " <<
            candidateIter->account << " has fee level of " <<
            feeLevelPaid << " needs at least " <<
            requiredFeeLevel;
        if (feeLevelPaid >= requiredFeeLevel)
        {
            auto firstTxn = candidateIter->txn;

            JLOG(j_.trace) << "Applying queued transaction " <<
                candidateIter->txID << " to open ledger.";

            // If the rules or flags change, preflight again
            assert(candidateIter->pfresult);
            if (candidateIter->pfresult->rules != view.rules() ||
                candidateIter->pfresult->flags != candidateIter->flags)
            {
                candidateIter->pfresult.emplace(
                    preflight(app, view.rules(),
                        candidateIter->pfresult->tx,
                            candidateIter->flags,
                                candidateIter->pfresult->j));
            }

            auto pcresult = preclaim(
                *candidateIter->pfresult, app, view);

            TER txnResult;
            bool didApply;
            std::tie(txnResult, didApply) = doApply(pcresult, app, view);

            if (didApply)
            {
                // Remove the candidate from the queue
                JLOG(j_.debug) << "Queued transaction " <<
                    candidateIter->txID <<
                    " applied successfully. Remove from queue.";

                candidateIter = erase(candidateIter);
                ledgerChanged = true;
            }
            else if (isTefFailure(txnResult) || isTemMalformed(txnResult) ||
                isTelLocal(txnResult))
            {
                JLOG(j_.debug) << "Queued transaction " <<
                    candidateIter->txID << " failed with " <<
                    transToken(txnResult) << ". Remove from queue.";
                candidateIter = erase(candidateIter);
            }
            else
            {
                JLOG(j_.debug) << "Transaction " <<
                    candidateIter->txID << " failed with " <<
                    transToken(txnResult) << ". Leave in queue.";
                candidateIter++;
            }

        }
        else
        {
            break;
        }
    }

    return ledgerChanged;
}
Beispiel #9
0
std::pair<TER, bool>
TxQ::apply(Application& app, OpenView& view,
    std::shared_ptr<STTx const> const& tx,
        ApplyFlags flags, beast::Journal j)
{
    auto const allowEscalation =
        (view.rules().enabled(featureFeeEscalation,
            app.config().features));
    if (!allowEscalation)
    {
        return ripple::apply(app, view, *tx, flags, j);
    }

    auto const account = (*tx)[sfAccount];
    auto currentSeq = true;

    std::lock_guard<std::mutex> lock(mutex_);

    // If there are other transactions in the queue
    // for this account, account for that before the pre-checks,
    // so we don't get a false terPRE_SEQ.
    auto accountIter = byAccount_.find(account);
    if (accountIter != byAccount_.end())
    {
        auto const sle = view.read(
            keylet::account(account));

        if (sle)
        {
            auto const& txQAcct = accountIter->second;
            auto const t_seq = tx->getSequence();
            auto const a_seq = sle->getFieldU32(sfSequence);

            currentSeq = a_seq == t_seq;
            for (auto seq = a_seq; !currentSeq && seq < t_seq; ++seq)
            {
                auto const existingCandidate =
                    txQAcct.findCandidateAt(seq);
                currentSeq = !existingCandidate;
            }
        }
    }

    // See if the transaction is likely to claim a fee.
    auto const pfresult = preflight(app, view.rules(),
        *tx, flags | (currentSeq ? tapNONE: tapPOST_SEQ), j);
    auto const pcresult = preclaim(pfresult, app, view);
    if (!pcresult.likelyToClaimFee)
        return{ pcresult.ter, false };

    auto const baseFee = pcresult.baseFee;
    auto const feeLevelPaid = getFeeLevelPaid(*tx,
        feeMetrics_.baseLevel, baseFee);
    auto const requiredFeeLevel = feeMetrics_.scaleFeeLevel(view);
    auto const transactionID = tx->getTransactionID();

    // Too low of a fee should get caught by preclaim
    assert(feeLevelPaid >= feeMetrics_.baseLevel);

    // Is there a transaction for the same account with the
    // same sequence number already in the queue?
    if (accountIter != byAccount_.end())
    {
        auto const sequence = tx->getSequence();
        auto& txQAcct = accountIter->second;
        auto existingCandidate = txQAcct.findCandidateAt(sequence);
        if (existingCandidate)
        {
            // Is the current transaction's fee higher than
            // the queued transaction's fee?
            // Don't care about the overflow flag
            auto requiredRetryLevel = mulDiv(
                existingCandidate->feeLevel,
                setup_.retrySequencePercent, 100).second;
            JLOG(j_.trace) << "Found transaction in queue for account " <<
                account << " with sequence number " << sequence <<
                " new txn fee level is " << feeLevelPaid <<
                ", old txn fee level is " <<
                existingCandidate->feeLevel <<
                ", new txn needs fee level of " <<
                requiredRetryLevel;
            if (feeLevelPaid > requiredRetryLevel
                || (existingCandidate->feeLevel < requiredFeeLevel &&
                    feeLevelPaid >= requiredFeeLevel))
            {
                // The fee is high enough to either retry or
                // the prior txn could not get into the open ledger,
                //  but this one can.
                // Remove the queued transaction and continue
                JLOG(j_.trace) <<
                    "Removing transaction from queue " <<
                    existingCandidate->txID <<
                    " in favor of " << transactionID;
                auto byFeeIter = byFee_.iterator_to(*existingCandidate);
                assert(byFeeIter != byFee_.end());
                assert(existingCandidate == &*byFeeIter);
                assert(byFeeIter->sequence == sequence);
                assert(byFeeIter->account == txQAcct.account);
                erase(byFeeIter);
            }
            else
            {
                // Drop the current transaction
                JLOG(j_.trace) <<
                    "Ignoring transaction " <<
                    transactionID <<
                    " in favor of queued " <<
                    existingCandidate->txID;
                return { telINSUF_FEE_P, false };
            }
        }
    }

    JLOG(j_.trace) << "Transaction " <<
        transactionID <<
        " from account " << account <<
        " has fee level of " << feeLevelPaid <<
        " needs at least " << requiredFeeLevel <<
        " to get in the open ledger, which has " <<
        view.txCount() << " entries.";

    // Can transaction go in open ledger?
    if (currentSeq && feeLevelPaid >= requiredFeeLevel)
    {
        // Transaction fee is sufficient to go in open ledger immediately

        JLOG(j_.trace) << "Applying transaction " <<
            transactionID <<
            " to open ledger.";
        ripple::TER txnResult;
        bool didApply;

        std::tie(txnResult, didApply) = doApply(pcresult, app, view);

        JLOG(j_.trace) << "Transaction " <<
            transactionID <<
                (didApply ? " applied successfully with " :
                    " failed with ") <<
                        transToken(txnResult);

        return { txnResult, didApply };
    }

    if (! canBeHeld(tx))
    {
        // Bail, transaction cannot be held
        JLOG(j_.trace) << "Transaction " <<
            transactionID <<
            " can not be held";
        return { feeLevelPaid >= requiredFeeLevel ?
            terPRE_SEQ : telINSUF_FEE_P, false };
    }

    // It's pretty unlikely that the queue will be "overfilled",
    // but should it happen, take the opportunity to fix it now.
    while (isFull())
    {
        auto lastRIter = byFee_.rbegin();
        if (feeLevelPaid > lastRIter->feeLevel)
        {
            // The queue is full, and this transaction is more
            // valuable, so kick out the cheapest transaction.
            JLOG(j_.warning) <<
                "Removing end item from queue with fee of" <<
                lastRIter->feeLevel << " in favor of " <<
                transactionID << " with fee of " <<
                feeLevelPaid;
            erase(byFee_.iterator_to(*lastRIter));
        }
        else
        {
            JLOG(j_.warning) << "Queue is full, and transaction " <<
                transactionID <<
                " fee is lower than end item";
            return { telINSUF_FEE_P, false };
        }
    }

    // Hold the transaction.
    // accountIter was already set when looking for duplicate seq.
    std::string op = "existing";
    if (accountIter == byAccount_.end())
    {
        // Create a new TxQAccount object and add the byAccount lookup.
        bool created;
        std::tie(accountIter, created) = byAccount_.emplace(
            account, TxQAccount(tx));
        (void)created;
        assert(created);
        op = "new";
    }
    auto& candidate = accountIter->second.addCandidate(
    { tx, transactionID, feeLevelPaid, flags, pfresult });
    // Then index it into the byFee lookup.
    byFee_.insert(candidate);
    JLOG(j_.debug) << "Added transaction " << candidate.txID <<
        " from " << op << " account " << candidate.account <<
        " to queue.";

    return { terQUEUED, false };
}