1use alloy_primitives::{Bytes, B256};
4use alloy_rpc_types_engine::{
5 ssz_engine_types::{Optional, PayloadStatus, PayloadStatusKind},
6 ExecutionData,
7};
8use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
9use reth_evm::{execute::Executor, ConfigureEvm};
10use reth_primitives_traits::{AlloyBlockHeader, Block};
11use reth_provider::{HeaderProvider, StateProvider, StateProviderBox, StateProviderFactory};
12use reth_revm::{database::StateProviderDatabase, witness::ExecutionWitnessRecord};
13use reth_tasks::Runtime;
14use reth_trie_common::ExecutionWitnessMode;
15use std::{future::Future, pin::Pin};
16
17#[derive(Clone, Debug)]
24pub struct EngineSszWitnessGenerator<Provider, Evm> {
25 provider: Provider,
26 evm_config: Evm,
27 task_spawner: Runtime,
28}
29
30impl<Provider, Evm> EngineSszWitnessGenerator<Provider, Evm> {
31 pub const fn new(provider: Provider, evm_config: Evm, task_spawner: Runtime) -> Self {
33 Self { provider, evm_config, task_spawner }
34 }
35}
36
37impl<Provider, Evm> EngineSszWitness for EngineSszWitnessGenerator<Provider, Evm>
38where
39 Provider: HeaderProvider + StateProviderFactory + Clone + Send + Sync + 'static,
40 Provider::Header: alloy_rlp::Encodable,
41 Evm: ConfigureEvm<Primitives = EthPrimitives> + 'static,
42{
43 fn generate_witness(
44 &self,
45 payload: ExecutionData,
46 ) -> Pin<
47 Box<
48 dyn Future<Output = Result<ExecutionWitnessV1, EngineSszWitnessError>> + Send + 'static,
49 >,
50 > {
51 let provider = self.provider.clone();
52 let evm_config = self.evm_config.clone();
53 let task_spawner = self.task_spawner.clone();
54
55 Box::pin(async move {
56 task_spawner
57 .spawn_blocking(move || {
58 let block = payload
60 .payload
61 .try_into_block_with_sidecar::<TransactionSigned>(&payload.sidecar)
62 .map_err(eyre::Report::new)?
63 .try_into_recovered()
64 .map_err(eyre::Report::new)?;
65
66 let block_number = block.header().number;
67 let parent_hash = block.header().parent_hash;
68 let state_provider =
69 provider.state_by_block_hash(parent_hash).map_err(|source| {
70 EngineSszWitnessError::ParentStateUnavailable {
71 parent: parent_hash,
72 source: eyre::Report::new(source),
73 }
74 })?;
75 let block_executor = evm_config.executor(StateProviderDatabase::new(
76 state_provider.into_evm_state_provider(),
77 ));
78 let mut witness = None;
79 let mut first_header = block_number.saturating_sub(1);
80 block_executor
81 .execute_with_state_closure(&block, |statedb: &reth_revm::State<_>| {
82 if let Some((number, _)) = statedb.block_hashes.lowest() {
83 first_header = number;
84 }
85 witness = Some(
86 ExecutionWitnessRecord::new(statedb)
87 .into_execution_witness_without_headers::<StateProviderBox>(
88 &statedb.database,
89 ExecutionWitnessMode::Canonical,
90 ),
91 );
92 })
93 .map_err(eyre::Report::new)?;
94
95 let witness = witness
96 .expect("state closure is called after successful execution")
97 .map_err(eyre::Report::new)?;
98
99 let mut headers = Vec::new();
101 let mut hash = parent_hash;
102 for _ in first_header..block_number {
103 let header = provider
104 .header(hash)
105 .map_err(eyre::Report::new)?
106 .ok_or_else(|| eyre::eyre!("ancestor {hash} not found for witness"))?;
107 hash = header.parent_hash();
108 headers.push(alloy_rlp::encode(&header).into());
109 }
110 headers.reverse();
111
112 Ok(ExecutionWitnessV1 { state: witness.state, codes: witness.codes, headers })
113 })
114 .await
115 .map_err(eyre::Report::new)?
116 })
117 }
118}
119
120pub trait EngineSszWitness: Send + Sync + 'static {
122 fn generate_witness(
124 &self,
125 payload: ExecutionData,
126 ) -> Pin<
127 Box<
128 dyn Future<Output = Result<ExecutionWitnessV1, EngineSszWitnessError>> + Send + 'static,
129 >,
130 >;
131}
132
133#[derive(Debug)]
135pub enum EngineSszWitnessError {
136 ParentStateUnavailable {
138 parent: B256,
140 source: eyre::Report,
142 },
143 Internal(eyre::Report),
145}
146
147impl From<eyre::Report> for EngineSszWitnessError {
148 fn from(error: eyre::Report) -> Self {
149 Self::Internal(error)
150 }
151}
152
153impl std::fmt::Display for EngineSszWitnessError {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 Self::ParentStateUnavailable { parent, source } => {
157 write!(f, "parent state {parent} is unavailable through the provider: {source}")
158 }
159 Self::Internal(error) => std::fmt::Display::fmt(error, f),
160 }
161 }
162}
163
164impl std::error::Error for EngineSszWitnessError {
165 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
166 match self {
167 Self::ParentStateUnavailable { source, .. } | Self::Internal(source) => {
168 Some(source.as_ref())
169 }
170 }
171 }
172}
173
174pub type WitnessNodeV1 = Bytes;
176
177pub type WitnessCodeV1 = Bytes;
179
180pub type WitnessHeaderV1 = Bytes;
182
183#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
191pub struct ExecutionWitnessV1 {
192 pub state: Vec<WitnessNodeV1>,
194 pub codes: Vec<WitnessCodeV1>,
196 pub headers: Vec<WitnessHeaderV1>,
198}
199
200pub type ExecutionWitness = ExecutionWitnessV1;
202
203#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode)]
210pub struct PayloadStatusWithWitness {
211 pub payload_status: PayloadStatus,
213 pub witness: Optional<ExecutionWitnessV1>,
215}
216
217impl PayloadStatusWithWitness {
218 pub fn new(payload_status: PayloadStatus, witness: Option<ExecutionWitnessV1>) -> Self {
220 let witness = match payload_status.status {
221 PayloadStatusKind::Valid => witness.into(),
222 _ => Optional::none(),
223 };
224 Self { payload_status, witness }
225 }
226}
227
228pub type NewPayloadWithWitnessResponseV1 = PayloadStatusWithWitness;
230
231impl ssz::Decode for PayloadStatusWithWitness {
232 fn is_ssz_fixed_len() -> bool {
233 false
234 }
235
236 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
237 let mut builder = ssz::SszDecoderBuilder::new(bytes);
238 builder.register_type::<PayloadStatus>()?;
239 builder.register_type::<Optional<ExecutionWitnessV1>>()?;
240 let mut decoder = builder.build()?;
241 let response =
242 Self { payload_status: decoder.decode_next()?, witness: decoder.decode_next()? };
243 if response.witness.is_some() && response.payload_status.status != PayloadStatusKind::Valid
244 {
245 return Err(ssz::DecodeError::BytesInvalid(
246 "execution witness is only valid for VALID payload status".into(),
247 ))
248 }
249 Ok(response)
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use ssz::{Decode, Encode};
257
258 fn assert_roundtrip<T>(value: &T)
259 where
260 T: Encode + Decode + PartialEq + core::fmt::Debug,
261 {
262 assert_eq!(T::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), *value);
263 }
264
265 #[test]
266 fn witness_response_roundtrips_when_status_is_valid() {
267 let payload_status = PayloadStatus {
268 status: PayloadStatusKind::Valid,
269 latest_valid_hash: Optional::none(),
270 validation_error: Optional::none(),
271 };
272 let witness = ExecutionWitnessV1 {
273 state: vec![vec![1, 2, 3].into()],
274 codes: vec![vec![4, 5].into()],
275 headers: vec![vec![6].into()],
276 };
277 let response = PayloadStatusWithWitness::new(payload_status, Some(witness));
278
279 assert_roundtrip(&response);
280 }
281
282 #[test]
283 fn witness_response_omits_witness_for_non_valid_status() {
284 let payload_status = PayloadStatus {
285 status: PayloadStatusKind::Syncing,
286 latest_valid_hash: Optional::none(),
287 validation_error: Optional::none(),
288 };
289 let response =
290 PayloadStatusWithWitness::new(payload_status, Some(ExecutionWitnessV1::default()));
291
292 assert!(response.witness.is_none());
293 assert_roundtrip(&response);
294 }
295}