1use alloy_consensus::{BlockHeader, Transaction};
4use alloy_primitives::Bytes;
5use alloy_rpc_types_engine::{ForkchoiceState, PayloadStatus};
6use futures::{stream::FuturesUnordered, Stream, StreamExt, TryFutureExt};
7use itertools::Either;
8use reth_chainspec::{ChainSpecProvider, EthChainSpec};
9use reth_engine_primitives::{
10 BeaconEngineMessage, BeaconOnNewPayloadError, ExecutionPayload as _, OnForkChoiceUpdated,
11};
12use reth_engine_tree::tree::EngineValidator;
13use reth_errors::{BlockExecutionError, BlockValidationError, RethError, RethResult};
14use reth_evm::{
15 database::StateProviderDatabase, BlockBuilder, BlockBuilderOutcome, BlockExecutor, ConfigureEvm,
16};
17use reth_payload_primitives::{BuiltPayload, PayloadTypes};
18use reth_primitives_traits::{
19 block::Block as _, BlockBody as _, BlockTy, HeaderTy, SealedBlock, SignedTransaction,
20};
21use reth_storage_api::{errors::ProviderError, BlockReader, StateProvider, StateProviderFactory};
22use std::{
23 collections::VecDeque,
24 future::Future,
25 pin::Pin,
26 task::{ready, Context, Poll},
27};
28use tokio::sync::oneshot;
29use tracing::*;
30
31#[derive(Debug)]
32enum EngineReorgState<T: PayloadTypes> {
33 Forward,
34 Reorg { queue: VecDeque<BeaconEngineMessage<T>> },
35}
36
37type EngineReorgResponse = Result<
38 Either<Result<PayloadStatus, BeaconOnNewPayloadError>, RethResult<OnForkChoiceUpdated>>,
39 oneshot::error::RecvError,
40>;
41
42type ReorgResponseFut = Pin<Box<dyn Future<Output = EngineReorgResponse> + Send + Sync>>;
43
44#[derive(Debug)]
46#[pin_project::pin_project]
47pub struct EngineReorg<S, T: PayloadTypes, Provider, Evm, Validator> {
48 #[pin]
50 stream: S,
51 provider: Provider,
53 evm_config: Evm,
55 payload_validator: Validator,
57 frequency: usize,
59 depth: usize,
61 forkchoice_states_forwarded: usize,
64 state: EngineReorgState<T>,
66 last_forkchoice_state: Option<ForkchoiceState>,
68 reorg_responses: FuturesUnordered<ReorgResponseFut>,
70}
71
72impl<S, T: PayloadTypes, Provider, Evm, Validator> EngineReorg<S, T, Provider, Evm, Validator> {
73 pub fn new(
75 stream: S,
76 provider: Provider,
77 evm_config: Evm,
78 payload_validator: Validator,
79 frequency: usize,
80 depth: usize,
81 ) -> Self {
82 Self {
83 stream,
84 provider,
85 evm_config,
86 payload_validator,
87 frequency,
88 depth,
89 state: EngineReorgState::Forward,
90 forkchoice_states_forwarded: 0,
91 last_forkchoice_state: None,
92 reorg_responses: FuturesUnordered::new(),
93 }
94 }
95}
96
97impl<S, T, Provider, Evm, Validator> Stream for EngineReorg<S, T, Provider, Evm, Validator>
98where
99 S: Stream<Item = BeaconEngineMessage<T>>,
100 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = Evm::Primitives>>,
101 Provider: BlockReader<Header = HeaderTy<Evm::Primitives>, Block = BlockTy<Evm::Primitives>>
102 + StateProviderFactory
103 + ChainSpecProvider,
104 Evm: ConfigureEvm,
105 Validator: EngineValidator<T, Evm::Primitives>,
106{
107 type Item = S::Item;
108
109 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
110 let mut this = self.project();
111
112 loop {
113 if let Poll::Ready(Some(response)) = this.reorg_responses.poll_next_unpin(cx) {
114 match response {
115 Ok(Either::Left(Ok(payload_status))) => {
116 debug!(target: "engine::stream::reorg", ?payload_status, "Received response for reorg new payload");
117 }
118 Ok(Either::Left(Err(payload_error))) => {
119 error!(target: "engine::stream::reorg", %payload_error, "Error on reorg new payload");
120 }
121 Ok(Either::Right(Ok(fcu_status))) => {
122 debug!(target: "engine::stream::reorg", ?fcu_status, "Received response for reorg forkchoice update");
123 }
124 Ok(Either::Right(Err(fcu_error))) => {
125 error!(target: "engine::stream::reorg", %fcu_error, "Error on reorg forkchoice update");
126 }
127 Err(_) => {}
128 };
129 continue
130 }
131
132 if let EngineReorgState::Reorg { queue } = &mut this.state {
133 match queue.pop_front() {
134 Some(msg) => return Poll::Ready(Some(msg)),
135 None => {
136 *this.forkchoice_states_forwarded = 0;
137 *this.state = EngineReorgState::Forward;
138 }
139 }
140 }
141
142 let next = ready!(this.stream.poll_next_unpin(cx));
143 let item = match (next, &this.last_forkchoice_state) {
144 (
145 Some(BeaconEngineMessage::NewPayload { cause, payload, tx }),
146 Some(last_forkchoice_state),
147 ) if this.forkchoice_states_forwarded > this.frequency &&
148 last_forkchoice_state.head_block_hash == payload.parent_hash() =>
150 {
151 let (reorg_block, encoded_bal) = match create_reorg_head(
161 this.provider,
162 this.evm_config,
163 this.payload_validator,
164 *this.depth,
165 payload.clone(),
166 ) {
167 Ok(result) => result,
168 Err(error) => {
169 error!(target: "engine::stream::reorg", %error, "Error attempting to create reorg head");
170 return Poll::Ready(Some(BeaconEngineMessage::NewPayload {
173 cause,
174 payload,
175 tx,
176 }))
177 }
178 };
179 let reorg_forkchoice_state = ForkchoiceState {
180 finalized_block_hash: last_forkchoice_state.finalized_block_hash,
181 safe_block_hash: last_forkchoice_state.safe_block_hash,
182 head_block_hash: reorg_block.hash(),
183 };
184
185 let (reorg_payload_tx, reorg_payload_rx) = oneshot::channel();
186 let (reorg_fcu_tx, reorg_fcu_rx) = oneshot::channel();
187 this.reorg_responses.extend([
188 Box::pin(reorg_payload_rx.map_ok(Either::Left)) as ReorgResponseFut,
189 Box::pin(reorg_fcu_rx.map_ok(Either::Right)) as ReorgResponseFut,
190 ]);
191
192 let queue = VecDeque::from([
193 BeaconEngineMessage::NewPayload { cause: cause.clone(), payload, tx },
195 BeaconEngineMessage::NewPayload {
197 cause: cause.clone(),
198 payload: T::block_to_payload(reorg_block, encoded_bal),
199 tx: reorg_payload_tx,
200 },
201 BeaconEngineMessage::ForkchoiceUpdated {
203 cause,
204 state: reorg_forkchoice_state,
205 payload_attrs: None,
206 tx: reorg_fcu_tx,
207 },
208 ]);
209 *this.state = EngineReorgState::Reorg { queue };
210 continue
211 }
212 (
213 Some(BeaconEngineMessage::ForkchoiceUpdated {
214 cause,
215 state,
216 payload_attrs,
217 tx,
218 }),
219 _,
220 ) => {
221 *this.last_forkchoice_state = Some(state);
225 *this.forkchoice_states_forwarded += 1;
226 Some(BeaconEngineMessage::ForkchoiceUpdated { cause, state, payload_attrs, tx })
227 }
228 (item, _) => item,
229 };
230 return Poll::Ready(item)
231 }
232 }
233}
234
235#[allow(clippy::type_complexity)]
236fn create_reorg_head<Provider, Evm, T, Validator>(
237 provider: &Provider,
238 evm_config: &Evm,
239 payload_validator: &Validator,
240 mut depth: usize,
241 next_payload: T::ExecutionData,
242) -> RethResult<(SealedBlock<BlockTy<Evm::Primitives>>, Option<Bytes>)>
243where
244 Provider: BlockReader<Header = HeaderTy<Evm::Primitives>, Block = BlockTy<Evm::Primitives>>
245 + StateProviderFactory
246 + ChainSpecProvider<ChainSpec: EthChainSpec>,
247 Evm: ConfigureEvm,
248 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = Evm::Primitives>>,
249 Validator: EngineValidator<T, Evm::Primitives>,
250{
251 let next_block =
253 payload_validator.convert_payload_to_block(next_payload).map_err(RethError::msg)?;
254
255 let mut previous_hash = next_block.parent_hash();
257 let mut candidate_transactions = next_block.into_body().transactions().to_vec();
258 let reorg_target = 'target: {
259 loop {
260 let reorg_target = provider
261 .block_by_hash(previous_hash)?
262 .ok_or_else(|| ProviderError::HeaderNotFound(previous_hash.into()))?;
263 if depth == 0 {
264 break 'target reorg_target.seal_slow()
265 }
266
267 depth -= 1;
268 previous_hash = reorg_target.header().parent_hash();
269 candidate_transactions = reorg_target.into_body().into_transactions();
270 }
271 };
272 let reorg_target_parent = provider
273 .sealed_header_by_hash(reorg_target.header().parent_hash())?
274 .ok_or_else(|| ProviderError::HeaderNotFound(reorg_target.header().parent_hash().into()))?;
275
276 debug!(target: "engine::stream::reorg", number = reorg_target.header().number(), hash = %previous_hash, "Selected reorg target");
277
278 let has_bal = reorg_target.header().block_access_list_hash().is_some();
279 let state_provider = provider.state_by_block_hash(reorg_target.header().parent_hash())?;
280 let evm_env = evm_config.evm_env(reorg_target.header()).map_err(RethError::other)?;
281 let evm = evm_config.evm_with_env(
282 StateProviderDatabase::new(state_provider.as_ref().into_evm_state_provider()),
283 evm_env.clone(),
284 );
285 let ctx = evm_config.context_for_block(&reorg_target).map_err(RethError::other)?;
286 let mut builder = evm_config.create_block_builder(evm, evm_env, &reorg_target_parent, ctx);
287 if has_bal {
288 builder.executor_mut().enable_block_access_list_builder();
289 }
290
291 builder.apply_pre_execution_changes()?;
292
293 for tx in candidate_transactions {
294 if builder.executor_mut().validate_transaction_gas_limit(tx.gas_limit()).is_err() {
296 continue
297 }
298
299 let tx_recovered =
300 tx.try_into_recovered().map_err(|_| ProviderError::SenderRecoveryError)?;
301 match builder.execute_transaction(tx_recovered) {
302 Ok(_) => {}
303 Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
304 hash,
305 error,
306 })) => {
307 trace!(target: "engine::stream::reorg", hash = %hash, ?error, "Error executing transaction from next block");
308 }
309 Err(error) => return Err(RethError::Execution(error)),
311 }
312 }
313
314 let BlockBuilderOutcome { block, block_access_list, .. } =
315 builder.finish(state_provider.as_ref(), None)?;
316
317 Ok((block.into_sealed_block(), block_access_list.map(|bal| bal.split().1)))
318}