Recover open ledger transactions to the queue (RIPD-1530):

* If the transaction can't be queued, recover to the open ledger once,
  and drop it on the next attempt.
* New result codes for transactions that can not queue.
* Add minimum queue size.
* Remove the obsolete and incorrect SF_RETRY flag.
* fix #2215
This commit is contained in:
Edward Hennis
2017-09-12 18:32:31 -04:00
parent 3bfd9de677
commit 62127d725d
16 changed files with 276 additions and 88 deletions

View File

@@ -339,7 +339,7 @@ TxQ::canBeHeld(STTx const& tx, OpenView const& view,
promise to stick around for long enough that it has
a realistic chance of getting into a ledger.
*/
auto lastValid = getLastLedgerSequence(tx);
auto const lastValid = getLastLedgerSequence(tx);
canBeHeld = !lastValid || *lastValid >=
view.info().seq + setup_.minimumLastLedgerBuffer;
}
@@ -349,10 +349,31 @@ TxQ::canBeHeld(STTx const& tx, OpenView const& view,
can queue. Mitigates the lost cost of relaying should
an early one fail or get dropped.
*/
canBeHeld = accountIter == byAccount_.end() ||
replacementIter ||
accountIter->second.getTxnCount() <
setup_.maximumTxnPerAccount;
// Allow if the account is not in the queue at all
canBeHeld = accountIter == byAccount_.end();
if(!canBeHeld)
{
// Allow this tx to replace another one
canBeHeld = replacementIter.is_initialized();
}
if (!canBeHeld)
{
// Allow if there are fewer than the limit
canBeHeld = accountIter->second.getTxnCount() <
setup_.maximumTxnPerAccount;
}
if (!canBeHeld)
{
// Allow if the transaction goes in front of any
// queued transactions. Enables recovery of open
// ledger transactions, and stuck transactions.
auto const tSeq = tx.getSequence();
canBeHeld = tSeq < accountIter->second.transactions.rbegin()->first;
}
}
return canBeHeld;
}
@@ -512,8 +533,7 @@ TxQ::tryClearAccountQueue(Application& app, OpenView& view,
non-blockers?
Yes: Remove the queued transaction. Continue to next
step.
No: Reject `txn` with `telINSUF_FEE_P` or
`telCAN_NOT_QUEUE`. Stop.
No: Reject `txn` with `telCAN_NOT_QUEUE_FEE`. Stop.
No: Continue to next step.
3. Does this tx have the expected sequence number for the
account?
@@ -527,11 +547,11 @@ TxQ::tryClearAccountQueue(Application& app, OpenView& view,
than the previous tx?
No: Reject with `telINSUF_FEE_P`. Stop.
Yes: Are any of the prior sequence txs blockers?
Yes: Reject with `telCAN_NOT_QUEUE`. Stop.
Yes: Reject with `telCAN_NOT_QUEUE_BLOCKED`. Stop.
No: Are the fees in-flight of the other
queued txs >= than the account
balance or minimum account reserve?
Yes: Reject with `telCAN_NOT_QUEUE`. Stop.
Yes: Reject with `telCAN_NOT_QUEUE_BALANCE`. Stop.
No: Create a throwaway sandbox `View`. Modify
the account's sequence number to match
the tx (avoid `terPRE_SEQ`), and decrease
@@ -550,8 +570,7 @@ TxQ::tryClearAccountQueue(Application& app, OpenView& view,
it to `doApply()` and return that result.
No: Continue to the next step.
6. Can the tx be held in the queue? (See TxQ::canBeHeld).
No: Reject `txn` with `telINSUF_FEE_P` if this tx
has the current sequence, or `telCAN_NOT_QUEUE`
No: Reject `txn` with `telCAN_NOT_QUEUE_FULL`
if not. Stop.
Yes: Continue to the next step.
7. Is the queue full?
@@ -613,8 +632,15 @@ TxQ::apply(Application& app, OpenView& view,
auto const baseFee = calculateBaseFee(app, view, *tx, j);
auto const feeLevelPaid = getFeeLevelPaid(*tx,
baseLevel, baseFee, setup_);
auto const requiredFeeLevel = FeeMetrics::scaleFeeLevel(
metricsSnapshot, view);
auto const requiredFeeLevel = [&]()
{
auto feeLevel = FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
if ((flags & tapPREFER_QUEUE) && byFee_.size())
{
return std::max(feeLevel, byFee_.begin()->feeLevel);
}
return feeLevel;
}();
auto accountIter = byAccount_.find(account);
bool const accountExists = accountIter != byAccount_.end();
@@ -679,8 +705,7 @@ TxQ::apply(Application& app, OpenView& view,
transactionID <<
" in favor of normal queued " <<
existingIter->second.txID;
return{existingIter == txQAcct.transactions.begin() ?
telINSUF_FEE_P : telCAN_NOT_QUEUE, false };
return {telCAN_NOT_QUEUE_BLOCKS, false };
}
}
}
@@ -710,7 +735,7 @@ TxQ::apply(Application& app, OpenView& view,
transactionID <<
" in favor of queued " <<
existingIter->second.txID;
return{ telINSUF_FEE_P, false };
return{ telCAN_NOT_QUEUE_FEE, false };
}
}
}
@@ -806,7 +831,7 @@ TxQ::apply(Application& app, OpenView& view,
transactionID <<
". A blocker-type transaction " <<
"is in the queue.";
return{ telCAN_NOT_QUEUE, false };
return{ telCAN_NOT_QUEUE_BLOCKED, false };
}
multiTxn->fee +=
workingIter->second.consequences->fee;
@@ -824,7 +849,7 @@ TxQ::apply(Application& app, OpenView& view,
than the account's current balance, or the
minimum reserve. If it is, then there's a risk
that the fees won't get paid, so drop this
transaction with a telCAN_NOT_QUEUE result.
transaction with a telCAN_NOT_QUEUE_BALANCE result.
TODO: Decide whether to count the current txn fee
in this limit if it's the last transaction for
this account. Currently, it will not count,
@@ -866,7 +891,7 @@ TxQ::apply(Application& app, OpenView& view,
"Ignoring transaction " <<
transactionID <<
". Total fees in flight too high.";
return{ telCAN_NOT_QUEUE, false };
return{ telCAN_NOT_QUEUE_BALANCE, false };
}
// Create the test view from the current view
@@ -904,24 +929,27 @@ TxQ::apply(Application& app, OpenView& view,
/* Quick heuristic check to see if it's worth checking that this
tx has a high enough fee to clear all the txs in the queue.
1) Must be an account already in the queue.
2) Must be have passed the multiTxn checks (tx is not the next
1) Transaction is trying to get into the open ledger
2) Must be an account already in the queue.
3) Must be have passed the multiTxn checks (tx is not the next
account seq, the skipped seqs are in the queue, the reserve
doesn't get exhausted, etc).
3) The next transaction must not have previously tried and failed
4) The next transaction must not have previously tried and failed
to apply to an open ledger.
4) Tx must be paying more than just the required fee level to
5) Tx must be paying more than just the required fee level to
get itself into the queue.
5) Fee level must be escalated above the default (if it's not,
6) Fee level must be escalated above the default (if it's not,
then the first tx _must_ have failed to process in `accept`
for some other reason. Tx is allowed to queue in case
conditions change, but don't waste the effort to clear).
6) Tx is not a 0-fee / free transaction, regardless of fee level.
7) Tx is not a 0-fee / free transaction, regardless of fee level.
*/
if (accountExists && multiTxn.is_initialized() &&
multiTxn->nextTxIter->second.retriesRemaining == MaybeTx::retriesAllowed &&
feeLevelPaid > requiredFeeLevel &&
requiredFeeLevel > baseLevel && baseFee != 0)
if (!(flags & tapPREFER_QUEUE) && accountExists &&
multiTxn.is_initialized() &&
multiTxn->nextTxIter->second.retriesRemaining ==
MaybeTx::retriesAllowed &&
feeLevelPaid > requiredFeeLevel &&
requiredFeeLevel > baseLevel && baseFee != 0)
{
OpenView sandbox(open_ledger, &view, view.rules());
@@ -970,8 +998,7 @@ TxQ::apply(Application& app, OpenView& view,
JLOG(j_.trace()) << "Transaction " <<
transactionID <<
" can not be held";
return { feeLevelPaid >= requiredFeeLevel ?
telCAN_NOT_QUEUE : telINSUF_FEE_P, false };
return { telCAN_NOT_QUEUE, false };
}
// If the queue is full, decide whether to drop the current
@@ -986,7 +1013,7 @@ TxQ::apply(Application& app, OpenView& view,
transactionID <<
" would kick a transaction from the same account (" <<
account << ") out of the queue.";
return { telCAN_NOT_QUEUE, false };
return { telCAN_NOT_QUEUE_FULL, false };
}
auto const& endAccount = byAccount_.at(lastRIter->account);
auto endEffectiveFeeLevel = [&]()
@@ -1038,7 +1065,7 @@ TxQ::apply(Application& app, OpenView& view,
JLOG(j_.warn()) << "Queue is full, and transaction " <<
transactionID <<
" fee is lower than end item's account average fee";
return { telINSUF_FEE_P, false };
return { telCAN_NOT_QUEUE_FULL, false };
}
}
@@ -1105,7 +1132,8 @@ TxQ::processClosedLedger(Application& app,
auto ledgerSeq = view.info().seq;
if (!timeLeap)
maxSize_ = snapshot.txnsExpected * setup_.ledgersInQueue;
maxSize_ = std::max (snapshot.txnsExpected * setup_.ledgersInQueue,
setup_.queueSizeMin);
// Remove any queued candidates whose LastLedgerSequence has gone by.
for(auto candidateIter = byFee_.begin(); candidateIter != byFee_.end(); )
@@ -1454,6 +1482,7 @@ setup_TxQ(Config const& config)
TxQ::Setup setup;
auto const& section = config.section("transaction_queue");
set(setup.ledgersInQueue, "ledgers_in_queue", section);
set(setup.queueSizeMin, "minimum_queue_size", section);
set(setup.retrySequencePercent, "retry_sequence_percent", section);
set(setup.multiTxnPercent, "multi_txn_percent", section);
set(setup.minimumEscalationMultiplier,