1use crate::eth::pool::{Pool, transactions::PoolTransaction};
4use alloy_primitives::TxHash;
5use futures::{
6 channel::mpsc::Receiver,
7 stream::{Fuse, StreamExt},
8 task::AtomicWaker,
9};
10use parking_lot::{RawRwLock, RwLock, lock_api::RwLockWriteGuard};
11use std::{
12 fmt,
13 marker::PhantomData,
14 pin::Pin,
15 sync::{
16 Arc,
17 atomic::{AtomicU64, Ordering},
18 },
19 task::{Context, Poll},
20 time::Duration,
21};
22use tokio::time::{Interval, MissedTickBehavior, Sleep};
23
24const INSTANT_COALESCE_WINDOW: Duration = Duration::from_millis(5);
27
28pub struct Miner<T> {
29 mode: Arc<RwLock<MiningMode>>,
31 generation: Arc<AtomicU64>,
33 inner: Arc<MinerInner>,
37 transaction: PhantomData<fn() -> T>,
39}
40
41impl<T> Clone for Miner<T> {
42 fn clone(&self) -> Self {
43 Self {
44 mode: self.mode.clone(),
45 generation: self.generation.clone(),
46 inner: self.inner.clone(),
47 transaction: PhantomData,
48 }
49 }
50}
51
52impl<T> fmt::Debug for Miner<T> {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.debug_struct("Miner").field("mode", &self.mode).finish_non_exhaustive()
55 }
56}
57
58impl<T> Miner<T> {
59 pub fn new(mode: MiningMode) -> Self {
61 Self {
62 mode: Arc::new(RwLock::new(mode)),
63 generation: Default::default(),
64 inner: Default::default(),
65 transaction: PhantomData,
66 }
67 }
68
69 pub fn mode_write(&self) -> RwLockWriteGuard<'_, RawRwLock, MiningMode> {
71 self.mode.write()
72 }
73
74 pub fn is_auto_mine(&self) -> bool {
76 let mode = self.mode.read();
77 matches!(*mode, MiningMode::Auto(_))
78 }
79
80 pub fn get_interval(&self) -> Option<u64> {
81 let mode = self.mode.read();
82 if let MiningMode::FixedBlockTime(ref mm) = *mode {
83 return Some(mm.interval.period().as_secs());
84 }
85 None
86 }
87
88 pub(crate) fn block_interval(&self) -> Option<Duration> {
90 let mode = self.mode.read();
91 match &*mode {
92 MiningMode::FixedBlockTime(miner) | MiningMode::Mixed(_, miner) => {
93 Some(miner.interval.period())
94 }
95 MiningMode::None | MiningMode::Auto(_) => None,
96 }
97 }
98
99 pub fn set_mining_mode(&self, mode: MiningMode) {
101 let new_mode = format!("{mode:?}");
102 let mut current = self.mode_write();
103 let mode = std::mem::replace(&mut *current, mode);
104 self.generation.fetch_add(1, Ordering::Relaxed);
105 drop(current);
106 trace!(target: "miner", "updated mining mode from {:?} to {}", mode, new_mode);
107 self.inner.wake();
108 }
109
110 pub(crate) fn handle_failed_candidate(&self, generation: u64) {
112 let mut mode = self.mode.write();
113 if self.generation.load(Ordering::Relaxed) != generation {
114 if let MiningMode::Auto(miner) | MiningMode::Mixed(miner, _) = &mut *mode {
115 miner.has_pending_txs = Some(true);
116 miner.coalesce = None;
117 }
118 return;
119 }
120 match &mut *mode {
121 MiningMode::Auto(miner) | MiningMode::Mixed(miner, _) => {
122 miner.has_pending_txs = Some(false);
123 miner.coalesce = None;
124 }
125 MiningMode::None | MiningMode::FixedBlockTime(_) => {}
126 }
127 match &mut *mode {
128 MiningMode::FixedBlockTime(miner) | MiningMode::Mixed(_, miner) => {
129 let period = miner.interval.period();
130 *miner = FixedBlockTimeMiner::new(period);
131 }
132 MiningMode::None | MiningMode::Auto(_) => {}
133 }
134 }
135
136 pub(crate) fn poll(
141 &mut self,
142 pool: &Arc<Pool<T>>,
143 cx: &mut Context<'_>,
144 ) -> Poll<MiningWork<T>> {
145 self.inner.register(cx);
146 let mut mode = self.mode.write();
147 let generation = self.generation.load(Ordering::Relaxed);
148 mode.poll(pool, cx).map(|transactions| MiningWork { transactions, generation })
149 }
150}
151
152pub(crate) struct MiningWork<T> {
154 pub(crate) transactions: Vec<Arc<PoolTransaction<T>>>,
155 pub(crate) generation: u64,
156}
157
158#[derive(Debug)]
160pub struct MinerInner {
161 waker: AtomicWaker,
162}
163
164impl MinerInner {
165 fn wake(&self) {
167 self.waker.wake();
168 }
169
170 fn register(&self, cx: &Context<'_>) {
171 self.waker.register(cx.waker());
172 }
173}
174
175impl Default for MinerInner {
176 fn default() -> Self {
177 Self { waker: AtomicWaker::new() }
178 }
179}
180
181#[derive(Debug)]
183pub enum MiningMode {
184 None,
186 Auto(ReadyTransactionMiner),
191 FixedBlockTime(FixedBlockTimeMiner),
193
194 Mixed(ReadyTransactionMiner, FixedBlockTimeMiner),
196}
197
198impl MiningMode {
199 pub fn instant(max_transactions: usize, listener: Receiver<TxHash>) -> Self {
200 Self::Auto(ReadyTransactionMiner {
201 max_transactions,
202 has_pending_txs: None,
203 rx: listener.fuse(),
204 coalesce: None,
205 })
206 }
207
208 pub fn interval(duration: Duration) -> Self {
209 Self::FixedBlockTime(FixedBlockTimeMiner::new(duration))
210 }
211
212 pub fn mixed(max_transactions: usize, listener: Receiver<TxHash>, duration: Duration) -> Self {
213 Self::Mixed(
214 ReadyTransactionMiner {
215 max_transactions,
216 has_pending_txs: None,
217 rx: listener.fuse(),
218 coalesce: None,
219 },
220 FixedBlockTimeMiner::new(duration),
221 )
222 }
223
224 pub fn poll<T>(
226 &mut self,
227 pool: &Arc<Pool<T>>,
228 cx: &mut Context<'_>,
229 ) -> Poll<Vec<Arc<PoolTransaction<T>>>> {
230 match self {
231 Self::None => Poll::Pending,
232 Self::Auto(miner) => miner.poll(pool, cx),
233 Self::FixedBlockTime(miner) => miner.poll(pool, cx),
234 Self::Mixed(auto, fixed) => {
235 let auto_txs = auto.poll(pool, cx);
236 let fixed_txs = fixed.poll(pool, cx);
237
238 match (auto_txs, fixed_txs) {
239 (Poll::Ready(mut auto_txs), Poll::Ready(fixed_txs)) => {
241 for tx in fixed_txs {
242 if auto_txs.iter().any(|auto_tx| auto_tx.hash() == tx.hash()) {
244 continue;
245 }
246 auto_txs.push(tx);
247 }
248 Poll::Ready(auto_txs)
249 }
250 (Poll::Ready(auto_txs), Poll::Pending) => Poll::Ready(auto_txs),
252 (Poll::Pending, fixed_txs) => fixed_txs,
255 }
256 }
257 }
258 }
259}
260
261#[derive(Debug)]
266pub struct FixedBlockTimeMiner {
267 interval: Interval,
269}
270
271impl FixedBlockTimeMiner {
272 pub fn new(duration: Duration) -> Self {
274 let start = tokio::time::Instant::now() + duration;
275 let mut interval = tokio::time::interval_at(start, duration);
276 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
279 Self { interval }
280 }
281
282 fn poll<T>(
283 &mut self,
284 pool: &Arc<Pool<T>>,
285 cx: &mut Context<'_>,
286 ) -> Poll<Vec<Arc<PoolTransaction<T>>>> {
287 if self.interval.poll_tick(cx).is_ready() {
288 return Poll::Ready(pool.ready_transactions().collect());
290 }
291 Poll::Pending
292 }
293}
294
295impl Default for FixedBlockTimeMiner {
296 fn default() -> Self {
297 Self::new(Duration::from_secs(6))
298 }
299}
300
301pub struct ReadyTransactionMiner {
303 max_transactions: usize,
305 has_pending_txs: Option<bool>,
307 rx: Fuse<Receiver<TxHash>>,
309 coalesce: Option<Pin<Box<Sleep>>>,
311}
312
313impl ReadyTransactionMiner {
314 fn poll<T>(
315 &mut self,
316 pool: &Arc<Pool<T>>,
317 cx: &mut Context<'_>,
318 ) -> Poll<Vec<Arc<PoolTransaction<T>>>> {
319 let mut saw_new_ready = false;
321 while let Poll::Ready(Some(_hash)) = self.rx.poll_next_unpin(cx) {
322 saw_new_ready = true;
323 }
324
325 if saw_new_ready {
328 self.has_pending_txs = Some(true);
329 if self.coalesce.is_none() {
330 self.coalesce = Some(Box::pin(tokio::time::sleep(INSTANT_COALESCE_WINDOW)));
331 }
332 }
333
334 if self.has_pending_txs == Some(false) {
335 return Poll::Pending;
336 }
337
338 if let Some(sleep) = self.coalesce.as_mut()
339 && sleep.as_mut().poll(cx).is_pending()
340 {
341 return Poll::Pending;
342 }
343 self.coalesce = None;
344
345 let transactions =
346 pool.ready_transactions().take(self.max_transactions).collect::<Vec<_>>();
347
348 self.has_pending_txs = Some(transactions.len() >= self.max_transactions);
350
351 if transactions.is_empty() {
352 self.has_pending_txs = Some(false);
353 return Poll::Pending;
354 }
355
356 Poll::Ready(transactions)
357 }
358}
359
360impl fmt::Debug for ReadyTransactionMiner {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 f.debug_struct("ReadyTransactionMiner")
363 .field("max_transactions", &self.max_transactions)
364 .finish_non_exhaustive()
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371 use futures::{channel::mpsc, future::poll_fn};
372
373 #[test]
374 fn stale_failure_resumes_replacement_autominer() {
375 let (_tx, rx) = mpsc::channel(1);
376 let miner = Miner::<()>::new(MiningMode::None);
377 miner.set_mining_mode(MiningMode::instant(1, rx));
378
379 miner.handle_failed_candidate(0);
380
381 let mode = miner.mode.read();
382 let MiningMode::Auto(auto) = &*mode else { panic!("expected auto mining") };
383 assert_eq!(auto.has_pending_txs, Some(true));
384 }
385
386 #[tokio::test]
387 async fn failed_fixed_candidate_rearms_interval() {
388 let mut miner = Miner::<()>::new(MiningMode::interval(Duration::from_millis(10)));
389 let pool = Arc::new(Pool::default());
390 tokio::time::timeout(Duration::from_secs(1), poll_fn(|cx| miner.poll(&pool, cx)))
391 .await
392 .unwrap();
393
394 miner.handle_failed_candidate(0);
395
396 tokio::time::timeout(Duration::from_secs(1), poll_fn(|cx| miner.poll(&pool, cx)))
397 .await
398 .unwrap();
399 }
400}