Skip to main content

reth_node_ethereum/
engine_ssz_witness.rs

1//! Execution witness generation for the SSZ Engine API extension.
2
3use crate::engine_ssz_containers::ExecutionWitnessV1;
4use alloy_primitives::B256;
5use alloy_rpc_types_engine::ExecutionData;
6use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
7use reth_evm::{execute::Executor, ConfigureEvm};
8use reth_primitives_traits::{AlloyBlockHeader, Block};
9use reth_provider::{HeaderProvider, StateProviderFactory};
10use reth_revm::{database::StateProviderDatabase, witness::ExecutionWitnessRecord};
11use reth_tasks::Runtime;
12use reth_trie_common::ExecutionWitnessMode;
13use std::{future::Future, pin::Pin};
14
15/// Re-executes validated payloads against their parent state for `/payloads/witness`.
16///
17/// The parent state must be available through the provider (persisted, canonical in-memory,
18/// or pending). Parents present only in the engine tree are reported as
19/// [`EngineSszWitnessError::ParentStateUnavailable`]; the route then answers with the payload
20/// status alone.
21#[derive(Clone, Debug)]
22pub struct EngineSszWitnessGenerator<Provider, Evm> {
23    provider: Provider,
24    evm_config: Evm,
25    task_spawner: Runtime,
26}
27
28impl<Provider, Evm> EngineSszWitnessGenerator<Provider, Evm> {
29    /// Creates a new witness generator.
30    pub const fn new(provider: Provider, evm_config: Evm, task_spawner: Runtime) -> Self {
31        Self { provider, evm_config, task_spawner }
32    }
33}
34
35impl<Provider, Evm> EngineSszWitness for EngineSszWitnessGenerator<Provider, Evm>
36where
37    Provider: HeaderProvider + StateProviderFactory + Clone + Send + Sync + 'static,
38    Provider::Header: alloy_rlp::Encodable,
39    Evm: ConfigureEvm<Primitives = EthPrimitives> + 'static,
40{
41    fn generate_witness(
42        &self,
43        payload: ExecutionData,
44    ) -> Pin<
45        Box<
46            dyn Future<Output = Result<ExecutionWitnessV1, EngineSszWitnessError>> + Send + 'static,
47        >,
48    > {
49        let provider = self.provider.clone();
50        let evm_config = self.evm_config.clone();
51        let task_spawner = self.task_spawner.clone();
52
53        Box::pin(async move {
54            task_spawner
55                .spawn_blocking(move || {
56                    // A VALID newPayload need not be canonical or visible through the provider.
57                    let block = payload
58                        .payload
59                        .try_into_block_with_sidecar::<TransactionSigned>(&payload.sidecar)
60                        .map_err(eyre::Report::new)?
61                        .try_into_recovered()
62                        .map_err(eyre::Report::new)?;
63
64                    let block_number = block.header().number;
65                    let parent_hash = block.header().parent_hash;
66                    let state_provider =
67                        provider.state_by_block_hash(parent_hash).map_err(|source| {
68                            EngineSszWitnessError::ParentStateUnavailable {
69                                parent: parent_hash,
70                                source: eyre::Report::new(source),
71                            }
72                        })?;
73                    let block_executor =
74                        evm_config.executor(StateProviderDatabase::new(state_provider));
75                    let mut witness = None;
76                    let mut first_header = block_number.saturating_sub(1);
77                    block_executor
78                        .execute_with_state_closure(&block, |statedb: &reth_revm::State<_>| {
79                            if let Some((number, _)) = statedb.block_hashes.lowest() {
80                                first_header = number;
81                            }
82                            witness = Some(
83                                ExecutionWitnessRecord::new(statedb)
84                                    .into_execution_witness_without_headers(
85                                        &statedb.database.0,
86                                        ExecutionWitnessMode::Canonical,
87                                    ),
88                            );
89                        })
90                        .map_err(eyre::Report::new)?;
91
92                    let witness = witness
93                        .expect("state closure is called after successful execution")
94                        .map_err(eyre::Report::new)?;
95
96                    // Header numbers may refer to a different canonical ancestor.
97                    let mut headers = Vec::new();
98                    let mut hash = parent_hash;
99                    for _ in first_header..block_number {
100                        let header = provider
101                            .header(hash)
102                            .map_err(eyre::Report::new)?
103                            .ok_or_else(|| eyre::eyre!("ancestor {hash} not found for witness"))?;
104                        hash = header.parent_hash();
105                        headers.push(alloy_rlp::encode(&header).into());
106                    }
107                    headers.reverse();
108
109                    Ok(ExecutionWitnessV1 { state: witness.state, codes: witness.codes, headers })
110                })
111                .await
112                .map_err(eyre::Report::new)?
113        })
114    }
115}
116
117/// Generates an execution witness for a valid payload.
118pub trait EngineSszWitness: Send + Sync + 'static {
119    /// Generates a REST-SSZ execution witness after the submitted payload has been validated.
120    fn generate_witness(
121        &self,
122        payload: ExecutionData,
123    ) -> Pin<
124        Box<
125            dyn Future<Output = Result<ExecutionWitnessV1, EngineSszWitnessError>> + Send + 'static,
126        >,
127    >;
128}
129
130/// Failure to produce a witness for a validated payload.
131#[derive(Debug)]
132pub enum EngineSszWitnessError {
133    /// The parent state is not available through the provider yet.
134    ParentStateUnavailable {
135        /// Parent block whose state is required.
136        parent: B256,
137        /// Provider failure while accessing the state.
138        source: eyre::Report,
139    },
140    /// Witness execution or proof generation failed.
141    Internal(eyre::Report),
142}
143
144impl From<eyre::Report> for EngineSszWitnessError {
145    fn from(error: eyre::Report) -> Self {
146        Self::Internal(error)
147    }
148}
149
150impl std::fmt::Display for EngineSszWitnessError {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        match self {
153            Self::ParentStateUnavailable { parent, source } => {
154                write!(f, "parent state {parent} is unavailable through the provider: {source}")
155            }
156            Self::Internal(error) => std::fmt::Display::fmt(error, f),
157        }
158    }
159}
160
161impl std::error::Error for EngineSszWitnessError {
162    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
163        match self {
164            Self::ParentStateUnavailable { source, .. } | Self::Internal(source) => {
165                Some(source.as_ref())
166            }
167        }
168    }
169}