1use alloy_primitives::{Bytes, B256};
4use alloy_rpc_types_engine::{
5 ssz_engine_types::{Optional, PayloadStatus, PayloadStatusKind},
6 ExecutionData,
7};
8use evm2::evm::{CacheDB, Db};
9use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
10use reth_evm::{
11 database::StateProviderDatabase, execute::Executor, witness::ExecutionWitnessRecord,
12 ConfigureEvm,
13};
14use reth_primitives_traits::{AlloyBlockHeader, Block};
15use reth_provider::{HeaderProvider, StateProvider, StateProviderFactory};
16use reth_tasks::Runtime;
17use reth_trie_common::ExecutionWitnessMode;
18use std::{future::Future, pin::Pin};
19
20#[derive(Clone, Debug)]
27pub struct EngineSszWitnessGenerator<Provider, Evm> {
28 provider: Provider,
29 evm_config: Evm,
30 task_spawner: Runtime,
31}
32
33impl<Provider, Evm> EngineSszWitnessGenerator<Provider, Evm> {
34 pub const fn new(provider: Provider, evm_config: Evm, task_spawner: Runtime) -> Self {
36 Self { provider, evm_config, task_spawner }
37 }
38}
39
40impl<Provider, Evm> EngineSszWitness for EngineSszWitnessGenerator<Provider, Evm>
41where
42 Provider: HeaderProvider + StateProviderFactory + Clone + 'static,
43 Provider::Header: alloy_rlp::Encodable,
44 Evm: ConfigureEvm<Primitives = EthPrimitives> + 'static,
45{
46 fn generate_witness(
47 &self,
48 payload: ExecutionData,
49 ) -> Pin<
50 Box<
51 dyn Future<Output = Result<ExecutionWitnessV1, EngineSszWitnessError>> + Send + 'static,
52 >,
53 > {
54 let provider = self.provider.clone();
55 let evm_config = self.evm_config.clone();
56 let task_spawner = self.task_spawner.clone();
57
58 Box::pin(async move {
59 task_spawner
60 .spawn_blocking(move || {
61 let block = payload
63 .payload
64 .try_into_block_with_sidecar::<TransactionSigned>(&payload.sidecar)
65 .map_err(eyre::Report::new)?
66 .try_into_recovered()
67 .map_err(eyre::Report::new)?;
68
69 let block_number = block.header().number;
70 let parent_hash = block.header().parent_hash;
71 let state_provider =
72 provider.state_by_block_hash(parent_hash).map_err(|source| {
73 EngineSszWitnessError::ParentStateUnavailable {
74 parent: parent_hash,
75 source: eyre::Report::new(source),
76 }
77 })?;
78 let mut db = CacheDB::new(Db::new(StateProviderDatabase::new(
79 state_provider.as_ref().into_evm_state_provider(),
80 )));
81 let output =
82 evm_config.executor(&mut db).execute(&block).map_err(eyre::Report::new)?;
83 let codes = ExecutionWitnessRecord::commit_execution(&mut db, &output.state);
84 let first_header = db
85 .cache
86 .block_hashes
87 .keys()
88 .map(|number| number.saturating_to::<u64>())
89 .min()
90 .unwrap_or_else(|| block_number.saturating_sub(1));
91 let witness = ExecutionWitnessRecord::new(&db)
92 .with_canonical_codes(codes)
93 .into_execution_witness_without_headers(
94 state_provider.as_ref(),
95 ExecutionWitnessMode::Canonical,
96 )
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)]
209pub struct PayloadStatusWithWitness {
210 pub payload_status: PayloadStatus,
212 pub witness: Optional<ExecutionWitnessV1>,
214}
215
216impl PayloadStatusWithWitness {
217 pub fn new(payload_status: PayloadStatus, witness: Option<ExecutionWitnessV1>) -> Self {
219 let witness = match payload_status.status {
220 PayloadStatusKind::Valid => witness.into(),
221 _ => Optional::none(),
222 };
223 Self { payload_status, witness }
224 }
225}
226
227pub type NewPayloadWithWitnessResponseV1 = PayloadStatusWithWitness;
229
230impl ssz::Decode for PayloadStatusWithWitness {
231 fn is_ssz_fixed_len() -> bool {
232 false
233 }
234
235 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
236 let mut builder = ssz::SszDecoderBuilder::new(bytes);
237 builder.register_type::<PayloadStatus>()?;
238 builder.register_type::<Optional<ExecutionWitnessV1>>()?;
239 let mut decoder = builder.build()?;
240 let response =
241 Self { payload_status: decoder.decode_next()?, witness: decoder.decode_next()? };
242 if response.witness.is_some() && response.payload_status.status != PayloadStatusKind::Valid
243 {
244 return Err(ssz::DecodeError::BytesInvalid(
245 "execution witness is only valid for VALID payload status".into(),
246 ))
247 }
248 Ok(response)
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use ssz::{Decode, Encode};
256
257 fn assert_roundtrip<T>(value: &T)
258 where
259 T: Encode + Decode + PartialEq + core::fmt::Debug,
260 {
261 assert_eq!(T::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), *value);
262 }
263
264 #[test]
265 fn witness_response_roundtrips_when_status_is_valid() {
266 let payload_status = PayloadStatus {
267 status: PayloadStatusKind::Valid,
268 latest_valid_hash: Optional::none(),
269 validation_error: Optional::none(),
270 };
271 let witness = ExecutionWitnessV1 {
272 state: vec![vec![1, 2, 3].into()],
273 codes: vec![vec![4, 5].into()],
274 headers: vec![vec![6].into()],
275 };
276 let response = PayloadStatusWithWitness::new(payload_status, Some(witness));
277
278 assert_roundtrip(&response);
279 }
280
281 #[test]
282 fn witness_response_omits_witness_for_non_valid_status() {
283 let payload_status = PayloadStatus {
284 status: PayloadStatusKind::Syncing,
285 latest_valid_hash: Optional::none(),
286 validation_error: Optional::none(),
287 };
288 let response =
289 PayloadStatusWithWitness::new(payload_status, Some(ExecutionWitnessV1::default()));
290
291 assert!(response.witness.is_none());
292 assert_roundtrip(&response);
293 }
294}