Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0d0eeee
fatp: Implement blockchain revert handling
AlexandruCihodaru Nov 28, 2025
dbbfe61
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Nov 28, 2025
40f6b28
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Nov 28, 2025
e0bb59f
fix prdoc
AlexandruCihodaru Nov 28, 2025
e36f7d3
feedback v1
AlexandruCihodaru Nov 28, 2025
d007072
Merge remote-tracking branch 'origin' into acihodaru/chain_event_revert
AlexandruCihodaru Nov 28, 2025
254eec1
Merge branch 'master' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 2, 2025
6d56226
feedback v2
AlexandruCihodaru Dec 2, 2025
8bf5055
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Dec 2, 2025
c08468b
Merge remote-tracking branch 'origin' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 2, 2025
4503871
on revert make hash finalized
AlexandruCihodaru Dec 2, 2025
7dce6b2
Merge branch 'master' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 2, 2025
65176b4
Update ChainEvent::Revert comment
AlexandruCihodaru Dec 2, 2025
55b3fcc
address comments
AlexandruCihodaru Dec 2, 2025
13cc7c8
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Dec 2, 2025
5e82db9
Clarify comments
AlexandruCihodaru Dec 2, 2025
b9f78fe
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Dec 2, 2025
0bd431f
Merge branch 'master' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 3, 2025
4e2e6ae
Merge branch 'master' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 3, 2025
6445574
Merge remote-tracking branch 'origin' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 3, 2025
02be84c
feedback
AlexandruCihodaru Dec 3, 2025
bab621b
Merge branch 'master' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 4, 2025
8063724
Merge remote-tracking branch 'origin' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 5, 2025
6ac56f8
feedback
AlexandruCihodaru Dec 5, 2025
2eb1d08
Merge branch 'master' into acihodaru/chain_event_revert
AlexandruCihodaru Dec 5, 2025
be7f2af
Merge branch 'master' into acihodaru/chain_event_revert
re-gius Jan 22, 2026
8a2b338
Add support for handling blockchain reverts (#10867)
re-gius Jan 22, 2026
cf5bec3
merge prdocs
re-gius Jan 22, 2026
fe9fb06
Merge branch 'master' into acihodaru/chain_event_revert
re-gius Jan 22, 2026
a948dcf
Merge branch 'master' into acihodaru/chain_event_revert
re-gius Jan 23, 2026
121a9be
decouple txn removal logic from chain revert handling
re-gius Jan 27, 2026
d254d83
Merge branch 'master' into acihodaru/chain_event_revert
re-gius Jan 27, 2026
f981da7
implement `remove_transactions` for MiddlewarePool
re-gius Jan 28, 2026
9cf4cba
clippy fix
re-gius Jan 28, 2026
c154749
semver fix
re-gius Jan 28, 2026
ebc3661
Merge branch 'master' into acihodaru/chain_event_revert
re-gius Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions prdoc/pr_10453.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
title: 'fatp: Implement blockchain revert handling'
doc:
- audience: Runtime Dev
description: |-
# Description

Adding support for handling blockchain reverts, useful in testing environments with RPCs like `evm_revert` or `anvil_reset`.

## Changes

### Chain Revert Handling

- Add `ChainEvent::Reverted` variant to represent backward blockchain progression
- Add `EnactmentAction::HandleReversion` to route revert events appropriately
- Implement `handle_reverted()` method that:
- Identifies and removes all views beyond the revert point (both active and inactive)
- Creates a fresh view at the new head populated from the current mempool state
- Atomically updates `most_recent_view` and cleans up listener references

`ChainEvent::Reverted` does NOT automatically remove transactions from the mempool.
Node builders should call `remove_transactions()` separately if they want to remove specific transactions (e.g., those from reverted blocks).

### New `remove_transactions` API

Added `remove_transactions` method to the `TransactionPool` trait that:
- Removes transactions from the pool **without banning** them (unlike `report_invalid`)
- Allows removed transactions to be resubmitted immediately
- Also removes dependent transactions (e.g., higher nonces from the same sender)
- Emits `TransactionStatus::Dropped` events to watchers for all removed transactions, including dependents
- Returns only the explicitly requested transactions that were actually removed (dependents not included in return value)

This API is decoupled from chain revert handling, giving node builders flexibility to decide which transactions to remove after a revert.

## Review Notes

- We always create a fresh view at the revert target block rather than reusing stale views, ensuring the view reflects the current mempool state
- Pending transactions will appear in the new view only if they pass revalidation against the new head's state
- `remove_transactions` mirrors `report_invalid` behavior but without banning, allowing resubmission
- View removal and most_recent_view update are performed atomically by holding all three locks (`most_recent_view`, `active_views`, `inactive_views`) simultaneously
crates:
- name: sc-transaction-pool-api
bump: major
- name: sc-transaction-pool
bump: minor
- name: sc-rpc-spec-v2
bump: patch
7 changes: 7 additions & 0 deletions substrate/bin/node/bench/src/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ impl sc_transaction_pool_api::TransactionPool for Transactions {
Default::default()
}

async fn remove_transactions(
&self,
_hashes: &[TxHash<Self>],
) -> Vec<Arc<Self::InPoolTransaction>> {
Default::default()
}

fn futures(&self) -> Vec<Self::InPoolTransaction> {
unimplemented!()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ impl TransactionPool for MiddlewarePool {
self.inner_pool.report_invalid(at, invalid_tx_errors).await
}

async fn remove_transactions(
&self,
hashes: &[TxHash<Self>],
) -> Vec<Arc<Self::InPoolTransaction>> {
self.inner_pool.remove_transactions(hashes).await
}

fn status(&self) -> PoolStatus {
self.inner_pool.status()
}
Expand Down
42 changes: 41 additions & 1 deletion substrate/client/transaction-pool/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,25 @@ pub trait TransactionPool: Send + Sync {
invalid_tx_errors: TxInvalidityReportMap<TxHash<Self>>,
) -> Vec<Arc<Self::InPoolTransaction>>;

/// Removes transactions from the pool without banning them.
///
/// Removed transactions can be resubmitted to the pool. Dependent transactions
/// (e.g., transactions with higher nonces from the same sender) are also removed.
/// This method emits [`TransactionStatus::Dropped`] events to watchers for all
/// removed transactions, including dependents.
///
/// Useful for scenarios like chain reverts where transactions need to be removed
/// but may be valid for resubmission. This method is decoupled from chain revert
/// handling to give node builders flexibility in their transaction management.
///
/// Transactions not present in the pool are silently ignored. Returns only the
/// explicitly requested transactions that were actually removed from the pool
/// (dependents are not included in the return value).
async fn remove_transactions(
&self,
hashes: &[TxHash<Self>],
) -> Vec<Arc<Self::InPoolTransaction>>;

// *** logging
/// Get futures transaction list.
fn futures(&self) -> Vec<Self::InPoolTransaction>;
Expand Down Expand Up @@ -389,13 +408,34 @@ pub enum ChainEvent<B: BlockT> {
/// Path from old finalized to new finalized parent.
tree_route: Arc<[B::Hash]>,
},
/// The chain has been reverted to an earlier state.
///
/// When this event is processed by the transaction pool:
/// - Views beyond the revert point are removed
/// - A fresh view is created at the new head, populated from the current mempool
/// - Transactions in the mempool are revalidated against the new head's state
///
/// **Important**: Transactions are NOT automatically removed from the mempool.
/// To remove transactions (e.g., those included in reverted blocks), use
/// `remove_transactions()` separately. This gives node builders flexibility to:
/// - Remove only transactions from reverted blocks
/// - Remove all transactions and require resubmission
/// - Keep all transactions and let revalidation filter them
///
/// This event is expected to be used in development and testing environments only.
Reverted {
/// Hash of the block we reverted to.
new_head: B::Hash,
},
}

impl<B: BlockT> ChainEvent<B> {
/// Returns the block hash associated to the event.
pub fn hash(&self) -> B::Hash {
match self {
Self::NewBestBlock { hash, .. } | Self::Finalized { hash, .. } => *hash,
Self::NewBestBlock { hash, .. } |
Self::Finalized { hash, .. } |
Self::Reverted { new_head: hash, .. } => *hash,
}
}

Expand Down
13 changes: 13 additions & 0 deletions substrate/client/transaction-pool/src/common/enactment_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ pub enum EnactmentAction<Block: BlockT> {
HandleEnactment(TreeRoute<Block>),
/// Enactment phase of maintenance shall be skipped
HandleFinalization,
/// Chain reversion to an older block shall be handled.
/// Primarily used in development and testing environments.
HandleReversion { new_head: Block::Hash },
}

impl<Block> EnactmentState<Block>
Expand Down Expand Up @@ -104,6 +107,12 @@ where
let new_hash = event.hash();
let finalized = event.is_finalized();

if let ChainEvent::Reverted { new_head } = event {
trace!(target: LOG_TARGET, "handle_enactment: chain is reverted.");
self.force_update(event);
return Ok(EnactmentAction::HandleReversion { new_head: *new_head });
}

// do not proceed with txpool maintain if block distance is too high
let skip_maintenance =
match (hash_to_number(new_hash), hash_to_number(self.recent_best_block)) {
Expand Down Expand Up @@ -177,6 +186,10 @@ where
match event {
ChainEvent::NewBestBlock { hash, .. } => self.recent_best_block = *hash,
ChainEvent::Finalized { hash, .. } => self.recent_finalized_block = *hash,
ChainEvent::Reverted { new_head, .. } => {
self.recent_best_block = *new_head;
self.recent_finalized_block = *new_head;
},
};
trace!(
target: LOG_TARGET,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ pub enum DroppedReason<Hash> {
LimitsEnforced,
/// Transaction was dropped because of being invalid.
Invalid,
/// Transaction was explicitly removed via the `remove_transactions` API.
/// This allows removal without banning, so transactions can be resubmitted.
Removed,
}

/// Dropped-logic related event from the single view.
Expand Down
Loading
Loading