1use crate::{
4 NodeResult,
5 eth::{
6 backend::validate::TransactionValidator, error::BlockchainError, fees::FeeHistoryService,
7 miner::Miner, pool::Pool,
8 },
9 filter::Filters,
10 mem::{Backend, storage::MinedBlockOutcome},
11};
12use alloy_consensus::TxReceipt;
13use alloy_network::Network;
14use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope};
15use futures::{FutureExt, Stream, StreamExt};
16use std::{
17 collections::VecDeque,
18 pin::Pin,
19 sync::Arc,
20 task::{Context, Poll},
21};
22use tokio::{task::JoinHandle, time::Interval};
23
24pub struct NodeService<N: Network>
31where
32 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
33{
34 pool: Arc<Pool<N::TxEnvelope>>,
36 block_producer: BlockProducer<N>,
38 miner: Miner<N::TxEnvelope>,
40 fee_history: FeeHistoryService<N>,
42 filters: Filters<N>,
44 filter_eviction_interval: Interval,
46}
47
48impl<N: Network> NodeService<N>
49where
50 Backend<N>: TransactionValidator<N::TxEnvelope>,
51 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
52{
53 pub fn new(
54 pool: Arc<Pool<N::TxEnvelope>>,
55 backend: Arc<Backend<N>>,
56 miner: Miner<N::TxEnvelope>,
57 fee_history: FeeHistoryService<N>,
58 filters: Filters<N>,
59 ) -> Self {
60 let start = tokio::time::Instant::now() + filters.keep_alive();
61 let filter_eviction_interval = tokio::time::interval_at(start, filters.keep_alive());
62 Self {
63 pool,
64 block_producer: BlockProducer::new(backend),
65 miner,
66 fee_history,
67 filter_eviction_interval,
68 filters,
69 }
70 }
71}
72
73impl<N: Network> Future for NodeService<N>
74where
75 Backend<N>: TransactionValidator<N::TxEnvelope>,
76 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
77{
78 type Output = NodeResult<()>;
79
80 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
81 let pin = self.get_mut();
82
83 loop {
86 while let Poll::Ready(Some(result)) = pin.block_producer.poll_next_unpin(cx) {
88 match result {
89 BlockProduction::Mined(outcome) => {
90 trace!(target: "node", "mined block {}", outcome.block_number);
91 pin.pool.on_mined_block(outcome);
92 }
93 BlockProduction::Failed(generation) => {
94 pin.miner.handle_failed_candidate(generation);
95 break;
96 }
97 }
98 }
99
100 if pin.block_producer.is_idle()
103 && let Poll::Ready(work) = pin.miner.poll(&pin.pool, cx)
104 {
105 pin.block_producer.queued.push_back(work);
107 } else {
108 break;
110 }
111 }
112
113 let _ = pin.fee_history.poll_unpin(cx);
115
116 if pin.filter_eviction_interval.poll_tick(cx).is_ready() {
117 let filters = pin.filters.clone();
118
119 tokio::task::spawn(async move { filters.evict().await });
121 }
122
123 Poll::Pending
124 }
125}
126
127type MiningResult<N> =
128 (Result<MinedBlockOutcome<<N as Network>::TxEnvelope>, BlockchainError>, Arc<Backend<N>>, u64);
129
130enum BlockProduction<T> {
131 Mined(MinedBlockOutcome<T>),
132 Failed(u64),
133}
134
135#[must_use = "streams do nothing unless polled"]
137struct BlockProducer<N: Network> {
138 idle_backend: Option<Arc<Backend<N>>>,
140 block_mining: Option<JoinHandle<MiningResult<N>>>,
142 queued: VecDeque<crate::eth::miner::MiningWork<N::TxEnvelope>>,
144}
145
146impl<N: Network> BlockProducer<N>
147where
148 Backend<N>: TransactionValidator<N::TxEnvelope>,
149 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
150{
151 fn new(backend: Arc<Backend<N>>) -> Self {
152 Self { idle_backend: Some(backend), block_mining: None, queued: Default::default() }
153 }
154
155 fn is_idle(&self) -> bool {
156 self.idle_backend.is_some() && self.block_mining.is_none() && self.queued.is_empty()
157 }
158}
159
160impl<N: Network> Stream for BlockProducer<N>
161where
162 Backend<N>: TransactionValidator<N::TxEnvelope> + Send + Sync + 'static,
163 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope> + 'static,
164{
165 type Item = BlockProduction<N::TxEnvelope>;
166
167 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
168 let pin = self.get_mut();
169
170 if !pin.queued.is_empty() {
171 if let Some(backend) = pin.idle_backend.take() {
173 let work = pin.queued.pop_front().expect("not empty; qed");
174 let generation = work.generation;
175
176 let handle = tokio::runtime::Handle::current();
179 let mining = tokio::task::spawn_blocking(move || {
180 handle.block_on(async move {
181 trace!(target: "miner", "creating new block");
182 let block = backend.mine_block(work.transactions).await;
183 if let Ok(block) = &block {
184 trace!(target: "miner", "created new block: {}", block.block_number);
185 }
186 (block, backend, generation)
187 })
188 });
189 pin.block_mining = Some(mining);
190 }
191 }
192
193 if let Some(mut mining) = pin.block_mining.take() {
194 if let Poll::Ready(res) = mining.poll_unpin(cx) {
195 return match res {
196 Ok((Ok(outcome), backend, _)) => {
197 pin.idle_backend = Some(backend);
198 Poll::Ready(Some(BlockProduction::Mined(outcome)))
199 }
200 Ok((Err(error), backend, generation)) => {
201 pin.idle_backend = Some(backend);
202 pin.queued.clear();
203 warn!(target: "miner", %error, "failed to finalize block");
204 Poll::Ready(Some(BlockProduction::Failed(generation)))
205 }
206 Err(err) => {
207 panic!("miner task failed: {err}");
208 }
209 };
210 }
211 pin.block_mining = Some(mining)
212 }
213
214 Poll::Pending
215 }
216}