anvil/eth/
miner.rs
1use 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 mode: Arc<RwLock<MiningMode>>,
23 inner: Arc<MinerInner>,
27 force_transactions: Option<Vec<Arc<PoolTransaction>>>,
30}
31
32impl Miner {
33 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 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 pub fn mode_write(&self) -> RwLockWriteGuard<'_, RawRwLock, MiningMode> {
57 self.mode.write()
58 }
59
60 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 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 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#[derive(Debug)]
104pub struct MinerInner {
105 waker: AtomicWaker,
106}
107
108impl MinerInner {
109 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#[derive(Debug)]
127pub enum MiningMode {
128 None,
130 Auto(ReadyTransactionMiner),
135 FixedBlockTime(FixedBlockTimeMiner),
137
138 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 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 (Poll::Ready(mut auto_txs), Poll::Ready(fixed_txs)) => {
179 for tx in fixed_txs {
180 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 (Poll::Ready(auto_txs), Poll::Pending) => Poll::Ready(auto_txs),
190 (Poll::Pending, fixed_txs) => fixed_txs,
193 }
194 }
195 }
196 }
197}
198
199#[derive(Debug)]
204pub struct FixedBlockTimeMiner {
205 interval: Interval,
207}
208
209impl FixedBlockTimeMiner {
210 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 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 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
235pub struct ReadyTransactionMiner {
237 max_transactions: usize,
239 has_pending_txs: Option<bool>,
241 rx: Fuse<Receiver<TxHash>>,
243}
244
245impl ReadyTransactionMiner {
246 fn poll(&mut self, pool: &Arc<Pool>, cx: &mut Context<'_>) -> Poll<Vec<Arc<PoolTransaction>>> {
247 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 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}