anvil/eth/
miner.rs

1//! Mines transactions
2
3use crate::eth::pool::{transactions::PoolTransaction, Pool};
4use alloy_primitives::TxHash;
5use futures::{
6    channel::mpsc::Receiver,
7    stream::{Fuse, StreamExt},
8    task::AtomicWaker,
9};
10use parking_lot::{lock_api::RwLockWriteGuard, RawRwLock, RwLock};
11use std::{
12    fmt,
13    sync::Arc,
14    task::{ready, Context, Poll},
15    time::Duration,
16};
17use tokio::time::{Interval, MissedTickBehavior};
18
19#[derive(Clone, Debug)]
20pub struct Miner {
21    /// The mode this miner currently operates in
22    mode: Arc<RwLock<MiningMode>>,
23    /// used for task wake up when the mining mode was forcefully changed
24    ///
25    /// This will register the task so we can manually wake it up if the mining mode was changed
26    inner: Arc<MinerInner>,
27    /// Transactions included into the pool before any others are.
28    /// Done once on startup.
29    force_transactions: Option<Vec<Arc<PoolTransaction>>>,
30}
31
32impl Miner {
33    /// Returns a new miner with that operates in the given `mode`.
34    pub fn new(mode: MiningMode) -> Self {
35        Self {
36            mode: Arc::new(RwLock::new(mode)),
37            inner: Default::default(),
38            force_transactions: None,
39        }
40    }
41
42    /// Provide transactions that will cause a block to be mined with transactions
43    /// as soon as the miner is polled.
44    /// Providing an empty list of transactions will cause the miner to mine an empty block assuming
45    /// there are not other transactions in the pool.
46    pub fn with_forced_transactions(
47        mut self,
48        force_transactions: Option<Vec<PoolTransaction>>,
49    ) -> Self {
50        self.force_transactions =
51            force_transactions.map(|tx| tx.into_iter().map(Arc::new).collect());
52        self
53    }
54
55    /// Returns the write lock of the mining mode
56    pub fn mode_write(&self) -> RwLockWriteGuard<'_, RawRwLock, MiningMode> {
57        self.mode.write()
58    }
59
60    /// Returns `true` if auto mining is enabled
61    pub fn is_auto_mine(&self) -> bool {
62        let mode = self.mode.read();
63        matches!(*mode, MiningMode::Auto(_))
64    }
65
66    pub fn get_interval(&self) -> Option<u64> {
67        let mode = self.mode.read();
68        if let MiningMode::FixedBlockTime(ref mm) = *mode {
69            return Some(mm.interval.period().as_secs())
70        }
71        None
72    }
73
74    /// Sets the mining mode to operate in
75    pub fn set_mining_mode(&self, mode: MiningMode) {
76        let new_mode = format!("{mode:?}");
77        let mode = std::mem::replace(&mut *self.mode_write(), mode);
78        trace!(target: "miner", "updated mining mode from {:?} to {}", mode, new_mode);
79        self.inner.wake();
80    }
81
82    /// polls the [Pool] and returns those transactions that should be put in a block according to
83    /// the current mode.
84    ///
85    /// May return an empty list, if no transactions are ready.
86    pub fn poll(
87        &mut self,
88        pool: &Arc<Pool>,
89        cx: &mut Context<'_>,
90    ) -> Poll<Vec<Arc<PoolTransaction>>> {
91        self.inner.register(cx);
92        let next = ready!(self.mode.write().poll(pool, cx));
93        if let Some(mut transactions) = self.force_transactions.take() {
94            transactions.extend(next);
95            Poll::Ready(transactions)
96        } else {
97            Poll::Ready(next)
98        }
99    }
100}
101
102/// A Mining mode that does nothing
103#[derive(Debug)]
104pub struct MinerInner {
105    waker: AtomicWaker,
106}
107
108impl MinerInner {
109    /// Call the waker again
110    fn wake(&self) {
111        self.waker.wake();
112    }
113
114    fn register(&self, cx: &Context<'_>) {
115        self.waker.register(cx.waker());
116    }
117}
118
119impl Default for MinerInner {
120    fn default() -> Self {
121        Self { waker: AtomicWaker::new() }
122    }
123}
124
125/// Mode of operations for the `Miner`
126#[derive(Debug)]
127pub enum MiningMode {
128    /// A miner that does nothing
129    None,
130    /// A miner that listens for new transactions that are ready.
131    ///
132    /// Either one transaction will be mined per block, or any number of transactions will be
133    /// allowed
134    Auto(ReadyTransactionMiner),
135    /// A miner that constructs a new block every `interval` tick
136    FixedBlockTime(FixedBlockTimeMiner),
137
138    /// A minner that uses both Auto and FixedBlockTime
139    Mixed(ReadyTransactionMiner, FixedBlockTimeMiner),
140}
141
142impl MiningMode {
143    pub fn instant(max_transactions: usize, listener: Receiver<TxHash>) -> Self {
144        Self::Auto(ReadyTransactionMiner {
145            max_transactions,
146            has_pending_txs: None,
147            rx: listener.fuse(),
148        })
149    }
150
151    pub fn interval(duration: Duration) -> Self {
152        Self::FixedBlockTime(FixedBlockTimeMiner::new(duration))
153    }
154
155    pub fn mixed(max_transactions: usize, listener: Receiver<TxHash>, duration: Duration) -> Self {
156        Self::Mixed(
157            ReadyTransactionMiner { max_transactions, has_pending_txs: None, rx: listener.fuse() },
158            FixedBlockTimeMiner::new(duration),
159        )
160    }
161
162    /// polls the [Pool] and returns those transactions that should be put in a block, if any.
163    pub fn poll(
164        &mut self,
165        pool: &Arc<Pool>,
166        cx: &mut Context<'_>,
167    ) -> Poll<Vec<Arc<PoolTransaction>>> {
168        match self {
169            Self::None => Poll::Pending,
170            Self::Auto(miner) => miner.poll(pool, cx),
171            Self::FixedBlockTime(miner) => miner.poll(pool, cx),
172            Self::Mixed(auto, fixed) => {
173                let auto_txs = auto.poll(pool, cx);
174                let fixed_txs = fixed.poll(pool, cx);
175
176                match (auto_txs, fixed_txs) {
177                    // Both auto and fixed transactions are ready, combine them
178                    (Poll::Ready(mut auto_txs), Poll::Ready(fixed_txs)) => {
179                        for tx in fixed_txs {
180                            // filter unique transactions
181                            if auto_txs.iter().any(|auto_tx| auto_tx.hash() == tx.hash()) {
182                                continue;
183                            }
184                            auto_txs.push(tx);
185                        }
186                        Poll::Ready(auto_txs)
187                    }
188                    // Only auto transactions are ready, return them
189                    (Poll::Ready(auto_txs), Poll::Pending) => Poll::Ready(auto_txs),
190                    // Only fixed transactions are ready or both are pending,
191                    // return fixed transactions or pending status
192                    (Poll::Pending, fixed_txs) => fixed_txs,
193                }
194            }
195        }
196    }
197}
198
199/// A miner that's supposed to create a new block every `interval`, mining all transactions that are
200/// ready at that time.
201///
202/// The default blocktime is set to 6 seconds
203#[derive(Debug)]
204pub struct FixedBlockTimeMiner {
205    /// The interval this fixed block time miner operates with
206    interval: Interval,
207}
208
209impl FixedBlockTimeMiner {
210    /// Creates a new instance with an interval of `duration`
211    pub fn new(duration: Duration) -> Self {
212        let start = tokio::time::Instant::now() + duration;
213        let mut interval = tokio::time::interval_at(start, duration);
214        // we use delay here, to ensure ticks are not shortened and to tick at multiples of interval
215        // from when tick was called rather than from start
216        interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
217        Self { interval }
218    }
219
220    fn poll(&mut self, pool: &Arc<Pool>, cx: &mut Context<'_>) -> Poll<Vec<Arc<PoolTransaction>>> {
221        if self.interval.poll_tick(cx).is_ready() {
222            // drain the pool
223            return Poll::Ready(pool.ready_transactions().collect())
224        }
225        Poll::Pending
226    }
227}
228
229impl Default for FixedBlockTimeMiner {
230    fn default() -> Self {
231        Self::new(Duration::from_secs(6))
232    }
233}
234
235/// A miner that Listens for new ready transactions
236pub struct ReadyTransactionMiner {
237    /// how many transactions to mine per block
238    max_transactions: usize,
239    /// stores whether there are pending transactions (if known)
240    has_pending_txs: Option<bool>,
241    /// Receives hashes of transactions that are ready
242    rx: Fuse<Receiver<TxHash>>,
243}
244
245impl ReadyTransactionMiner {
246    fn poll(&mut self, pool: &Arc<Pool>, cx: &mut Context<'_>) -> Poll<Vec<Arc<PoolTransaction>>> {
247        // always drain the notification stream so that we're woken up as soon as there's a new tx
248        while let Poll::Ready(Some(_hash)) = self.rx.poll_next_unpin(cx) {
249            self.has_pending_txs = Some(true);
250        }
251
252        if self.has_pending_txs == Some(false) {
253            return Poll::Pending
254        }
255
256        let transactions =
257            pool.ready_transactions().take(self.max_transactions).collect::<Vec<_>>();
258
259        // there are pending transactions if we didn't drain the pool
260        self.has_pending_txs = Some(transactions.len() >= self.max_transactions);
261
262        if transactions.is_empty() {
263            return Poll::Pending
264        }
265
266        Poll::Ready(transactions)
267    }
268}
269
270impl fmt::Debug for ReadyTransactionMiner {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        f.debug_struct("ReadyTransactionMiner")
273            .field("max_transactions", &self.max_transactions)
274            .finish_non_exhaustive()
275    }
276}