Skip to content

Commit 2ab3a7a

Browse files
michalkucharczykiulianbarbu
authored andcommitted
fatxpool: proper handling of priorities when mempool is full (paritytech#6647)
Higher-priority transactions can now replace lower-priority transactions even when the internal _tx_mem_pool_ is full. **Notes for reviewers:** - The _tx_mem_pool_ now maintains information about transaction priority. Although _tx_mem_pool_ itself is stateless, transaction priority is updated after submission to the view. An alternative approach could involve validating transactions at the `at` block, but this is computationally expensive. To avoid additional validation overhead, I opted to use the priority obtained from runtime during submission to the view. This is the rationale behind introducing the `SubmitOutcome` struct, which synchronously communicates transaction priority from the view to the pool. This results in a very brief window during which the transaction priority remains unknown - those transaction are not taken into consideration while dropping takes place. In the future, if needed, we could update transaction priority using view revalidation results to keep this information fully up-to-date (as priority of transaction may change with chain-state evolution). - When _tx_mem_pool_ becomes full (an event anticipated to be rare), transaction priority must be known to perform priority-based removal. In such cases, the most recent block known is utilized for validation. I think that speculative submission to the view and re-using the priority from this submission would be an unnecessary complication. - Once the priority is determined, lower-priority transactions whose cumulative size meets or exceeds the size of the new transaction are collected to ensure the pool size limit is not exceeded. - Transaction removed from _tx_mem_pool_ , also needs to be removed from all the views with appropriate event (which is done by `remove_transaction_subtree`). To ensure complete removal, the `PendingTxReplacement` struct was re-factored to more generic `PendingPreInsertTask` (introduced in paritytech#6405) which covers removal and submssion of transaction in the view which may be potentially created in the background. This is to ensure that removed transaction will not re-enter to the newly created view. - `submit_local` implementation was also improved to properly handle priorities in case when mempool is full. Some missing tests for this method were also added. Closes: paritytech#5809 --------- Co-authored-by: command-bot <> Co-authored-by: Iulian Barbu <[email protected]>
1 parent 585b44e commit 2ab3a7a

19 files changed

Lines changed: 1393 additions & 251 deletions

File tree

prdoc/pr_6647.prdoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
title: '`fatxpool`: proper handling of priorities when mempool is full'
2+
doc:
3+
- audience: Node Dev
4+
description: |-
5+
Higher-priority transactions can now replace lower-priority transactions even when the internal _tx_mem_pool_ is full.
6+
crates:
7+
- name: sc-transaction-pool
8+
bump: minor

substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ pub struct DroppedTransaction<Hash> {
5353
}
5454

5555
impl<Hash> DroppedTransaction<Hash> {
56-
fn new_usurped(tx_hash: Hash, by: Hash) -> Self {
56+
/// Creates a new instance with reason set to `DroppedReason::Usurped(by)`.
57+
pub fn new_usurped(tx_hash: Hash, by: Hash) -> Self {
5758
Self { reason: DroppedReason::Usurped(by), tx_hash }
5859
}
5960

60-
fn new_enforced_by_limts(tx_hash: Hash) -> Self {
61+
/// Creates a new instance with reason set to `DroppedReason::LimitsEnforced`.
62+
pub fn new_enforced_by_limts(tx_hash: Hash) -> Self {
6163
Self { reason: DroppedReason::LimitsEnforced, tx_hash }
6264
}
6365
}
@@ -256,11 +258,13 @@ where
256258
self.future_transaction_views.entry(tx_hash).or_default().insert(block_hash);
257259
},
258260
TransactionStatus::Ready | TransactionStatus::InBlock(..) => {
259-
// note: if future transaction was once seens as the ready we may want to treat it
260-
// as ready transactions. Unreferenced future transactions are more likely to be
261-
// removed when the last referencing view is removed then ready transactions.
262-
// Transcaction seen as ready is likely quite close to be included in some
263-
// future fork.
261+
// note: if future transaction was once seen as the ready we may want to treat it
262+
// as ready transaction. The rationale behind this is as follows: we want to remove
263+
// unreferenced future transactions when the last referencing view is removed (to
264+
// avoid clogging mempool). For ready transactions we prefer to keep them in mempool
265+
// even if no view is currently referencing them. Future transcaction once seen as
266+
// ready is likely quite close to be included in some future fork (it is close to be
267+
// ready, so we make exception and treat such transaction as ready).
264268
if let Some(mut views) = self.future_transaction_views.remove(&tx_hash) {
265269
views.insert(block_hash);
266270
self.ready_transaction_views.insert(tx_hash, views);

0 commit comments

Comments
 (0)