Skip to main content

pop_fork/
txpool.rs

1// SPDX-License-Identifier: GPL-3.0
2
3//! Minimal transaction pool for collecting submitted extrinsics.
4//!
5//! This is a simple FIFO queue with no validation or ordering,
6//! designed for fork/simulation tools where production-grade
7//! transaction pool complexity is unnecessary.
8
9use crate::TxPoolError;
10use std::sync::RwLock;
11use subxt::config::substrate::H256;
12
13/// A minimal transaction pool that stores pending extrinsics.
14///
15/// Thread-safe FIFO queue for extrinsics awaiting inclusion in a block.
16/// Does not perform validation or ordering - extrinsics are processed
17/// in submission order.
18#[derive(Default)]
19pub struct TxPool {
20	pending: RwLock<Vec<Vec<u8>>>,
21}
22
23impl TxPool {
24	/// Create a new empty transaction pool.
25	pub fn new() -> Self {
26		Self { pending: RwLock::new(Vec::new()) }
27	}
28
29	/// Submit an extrinsic to the pool.
30	///
31	/// Returns the blake2-256 hash of the extrinsic.
32	pub fn submit(&self, extrinsic: Vec<u8>) -> Result<H256, TxPoolError> {
33		let hash = H256::from(sp_core::blake2_256(&extrinsic));
34		self.pending
35			.write()
36			.map_err(|err| TxPoolError::Lock(err.to_string()))?
37			.push(extrinsic);
38		Ok(hash)
39	}
40
41	/// Drain all pending extrinsics from the pool.
42	///
43	/// Returns all extrinsics in FIFO order and clears the pool.
44	/// Used by block builder to collect transactions for inclusion.
45	pub fn drain(&self) -> Result<Vec<Vec<u8>>, TxPoolError> {
46		Ok(std::mem::take(
47			&mut *self.pending.write().map_err(|err| TxPoolError::Lock(err.to_string()))?,
48		))
49	}
50
51	/// Submit an extrinsic and immediately drain all pending extrinsics.
52	///
53	/// This combines `submit` and `drain` into a single lock acquisition,
54	/// which is more efficient for instant-mode block building where we
55	/// build immediately after receiving a transaction.
56	///
57	/// Returns a tuple of (extrinsic hash, all pending extrinsics including the new one).
58	pub fn submit_and_drain(
59		&self,
60		extrinsic: Vec<u8>,
61	) -> Result<(H256, Vec<Vec<u8>>), TxPoolError> {
62		let hash = H256::from(sp_core::blake2_256(&extrinsic));
63		let mut pending = self.pending.write().map_err(|err| TxPoolError::Lock(err.to_string()))?;
64		pending.push(extrinsic);
65		let all = std::mem::take(&mut *pending);
66		Ok((hash, all))
67	}
68
69	/// Get a clone of all pending extrinsics without removing them.
70	pub fn pending(&self) -> Result<Vec<Vec<u8>>, TxPoolError> {
71		Ok(self.pending.read().map_err(|err| TxPoolError::Lock(err.to_string()))?.clone())
72	}
73
74	/// Returns the number of pending extrinsics.
75	pub fn len(&self) -> Result<usize, TxPoolError> {
76		Ok(self.pending.read().map_err(|err| TxPoolError::Lock(err.to_string()))?.len())
77	}
78
79	/// Returns true if there are no pending extrinsics.
80	pub fn is_empty(&self) -> Result<bool, TxPoolError> {
81		Ok(self.len()? == 0)
82	}
83}
84
85#[cfg(test)]
86mod tests {
87	use super::*;
88
89	#[test]
90	fn submit_returns_correct_hash() {
91		let pool = TxPool::new();
92		let extrinsic = vec![1, 2, 3, 4];
93		let expected_hash = H256::from(sp_core::blake2_256(&extrinsic));
94
95		let hash = pool.submit(extrinsic).unwrap();
96
97		assert_eq!(hash, expected_hash);
98	}
99
100	#[test]
101	fn drain_returns_all_extrinsics_in_fifo_order() {
102		let pool = TxPool::new();
103		pool.submit(vec![1]).unwrap();
104		pool.submit(vec![2]).unwrap();
105		pool.submit(vec![3]).unwrap();
106
107		let drained = pool.drain().unwrap();
108
109		assert_eq!(drained, vec![vec![1], vec![2], vec![3]]);
110		assert!(pool.is_empty().unwrap());
111	}
112
113	#[test]
114	fn pending_returns_extrinsics_without_removing() {
115		let pool = TxPool::new();
116		pool.submit(vec![1, 2]).unwrap();
117		pool.submit(vec![3, 4]).unwrap();
118
119		let pending = pool.pending().unwrap();
120
121		assert_eq!(pending, vec![vec![1, 2], vec![3, 4]]);
122		assert_eq!(pool.len().unwrap(), 2);
123	}
124
125	#[test]
126	fn submit_and_drain_returns_hash_and_all_pending() {
127		let pool = TxPool::new();
128		pool.submit(vec![1]).unwrap();
129		pool.submit(vec![2]).unwrap();
130
131		let new_extrinsic = vec![3, 4, 5];
132		let expected_hash = H256::from(sp_core::blake2_256(&new_extrinsic));
133
134		let (hash, drained) = pool.submit_and_drain(new_extrinsic).unwrap();
135
136		assert_eq!(hash, expected_hash);
137		assert_eq!(drained, vec![vec![1], vec![2], vec![3, 4, 5]]);
138		assert!(pool.is_empty().unwrap());
139	}
140}