1use std::{fmt::Debug, marker::PhantomData};
2
3use alloy_consensus::{Header, Sealed};
4use alloy_eips::{eip4788::BEACON_ROOTS_ADDRESS, BlockId};
5use alloy_primitives::{B256, U256};
6use alloy_provider::{network::AnyNetwork, Provider};
7use async_trait::async_trait;
8use ethereum_consensus::ssz::prelude::Prove;
9use rsp_mpt::EthereumState;
10use sp1_cc_client_executor::{
11 get_beacon_root_from_state, rebuild_merkle_root, Anchor, BeaconAnchor, BeaconAnchorId,
12 BeaconBlockField, BeaconStateAnchor, BeaconWithHeaderAnchor, ChainedBeaconAnchor,
13 HISTORY_BUFFER_LENGTH,
14};
15use url::Url;
16
17use crate::{
18 beacon::{BeaconClient, SignedBeaconBlock},
19 HostError,
20};
21
22#[async_trait]
24pub trait AnchorBuilder {
25 async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError>;
26}
27
28#[async_trait]
30pub trait BeaconAnchorKind: Sized {
31 async fn build_beacon_anchor_from_header<P: Provider<AnyNetwork>>(
32 header: &Sealed<Header>,
33 field: BeaconBlockField,
34 beacon_anchor_builder: &BeaconAnchorBuilder<P, Self>,
35 ) -> Result<(B256, BeaconAnchor), HostError>;
36}
37
38#[derive(Debug)]
40pub struct Eip4788BeaconAnchor;
41
42#[async_trait]
43impl BeaconAnchorKind for Eip4788BeaconAnchor {
44 async fn build_beacon_anchor_from_header<P: Provider<AnyNetwork>>(
45 header: &Sealed<Header>,
46 field: BeaconBlockField,
47 beacon_anchor_builder: &BeaconAnchorBuilder<P, Self>,
48 ) -> Result<(B256, BeaconAnchor), HostError> {
49 let child_header =
50 beacon_anchor_builder.header_anchor_builder.get_header(header.number + 1).await?;
51 assert_eq!(child_header.parent_hash, header.seal());
52
53 let beacon_root = child_header
54 .parent_beacon_block_root
55 .ok_or_else(|| HostError::ParentBeaconBlockRootMissing)?;
56
57 let anchor = beacon_anchor_builder
58 .build_beacon_anchor(
59 beacon_root,
60 BeaconAnchorId::Timestamp(child_header.timestamp),
61 field,
62 )
63 .await?;
64
65 Ok((beacon_root, anchor))
66 }
67}
68
69#[derive(Debug)]
71pub struct ConsensusBeaconAnchor;
72
73#[async_trait]
74impl BeaconAnchorKind for ConsensusBeaconAnchor {
75 async fn build_beacon_anchor_from_header<P: Provider<AnyNetwork>>(
76 header: &Sealed<Header>,
77 field: BeaconBlockField,
78 beacon_anchor_builder: &BeaconAnchorBuilder<P, Self>,
79 ) -> Result<(B256, BeaconAnchor), HostError> {
80 let parent_root = header
81 .parent_beacon_block_root
82 .ok_or_else(|| HostError::ParentBeaconBlockRootMissing)?;
83
84 let (beacon_root, beacon_header) = beacon_anchor_builder
85 .client
86 .get_header_from_parent_root(parent_root.to_string())
87 .await?;
88
89 let anchor = beacon_anchor_builder
90 .build_beacon_anchor(
91 beacon_root,
92 BeaconAnchorId::Slot(beacon_header.message.slot),
93 field,
94 )
95 .await?;
96
97 Ok((beacon_root, anchor))
98 }
99}
100
101#[derive(Debug)]
105pub struct HeaderAnchorBuilder<P> {
106 provider: P,
107}
108
109impl<P> HeaderAnchorBuilder<P> {
110 pub fn new(provider: P) -> Self {
111 Self { provider }
112 }
113}
114
115impl<P: Provider<AnyNetwork>> HeaderAnchorBuilder<P> {
116 pub async fn get_header<B: Into<BlockId>>(
117 &self,
118 block_id: B,
119 ) -> Result<Sealed<Header>, HostError> {
120 let block_id = block_id.into();
121 let block = self
122 .provider
123 .get_block(block_id)
124 .await?
125 .ok_or_else(|| HostError::BlockNotFoundError(block_id))?;
126
127 let header = block
128 .header
129 .inner
130 .clone()
131 .try_into_header()
132 .map_err(|_| HostError::HeaderConversionError(block.inner.header.number))?;
133
134 Ok(Sealed::new(header))
135 }
136}
137
138#[async_trait]
139impl<P: Provider<AnyNetwork>> AnchorBuilder for HeaderAnchorBuilder<P> {
140 async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
141 let header = self.get_header(block_id).await?;
142
143 Ok(header.into_inner().into())
144 }
145}
146
147pub struct BeaconAnchorBuilder<P, K> {
149 header_anchor_builder: HeaderAnchorBuilder<P>,
150 client: BeaconClient,
151 phantom: PhantomData<K>,
152}
153
154impl<P> BeaconAnchorBuilder<P, Eip4788BeaconAnchor> {
155 pub fn new(header_anchor_builder: HeaderAnchorBuilder<P>, cl_rpc_url: Url) -> Self {
157 Self { header_anchor_builder, client: BeaconClient::new(cl_rpc_url), phantom: PhantomData }
158 }
159
160 pub fn into_consensus(self) -> BeaconAnchorBuilder<P, ConsensusBeaconAnchor> {
162 BeaconAnchorBuilder {
163 header_anchor_builder: self.header_anchor_builder,
164 client: self.client,
165 phantom: PhantomData,
166 }
167 }
168}
169
170impl<P: Provider<AnyNetwork>, K: BeaconAnchorKind> BeaconAnchorBuilder<P, K> {
171 pub async fn build_beacon_anchor_with_header(
173 &self,
174 header: &Sealed<Header>,
175 field: BeaconBlockField,
176 ) -> Result<BeaconWithHeaderAnchor, HostError> {
177 let (beacon_root, anchor) = K::build_beacon_anchor_from_header(header, field, self).await?;
178
179 if matches!(field, BeaconBlockField::BlockHash) {
180 assert!(
181 verify_merkle_root(header.seal(), anchor.proof(), usize::from(&field), beacon_root),
182 "the proof verification fail, field: {field}",
183 );
184 }
185
186 Ok(BeaconWithHeaderAnchor::new(header.clone_inner(), anchor))
187 }
188
189 pub async fn build_beacon_anchor(
191 &self,
192 beacon_root: B256,
193 id: BeaconAnchorId,
194 field: BeaconBlockField,
195 ) -> Result<BeaconAnchor, HostError> {
196 let signed_beacon_block = self.client.get_block(beacon_root.to_string()).await?;
197
198 let (proof, _) = match signed_beacon_block {
199 SignedBeaconBlock::Deneb(signed_beacon_block) => {
200 signed_beacon_block.message.prove(&[
201 "body".into(),
202 "execution_payload".into(),
203 field.to_string().as_str().into(),
204 ])?
205 }
206 SignedBeaconBlock::Electra(signed_beacon_block) => {
207 signed_beacon_block.message.prove(&[
208 "body".into(),
209 "execution_payload".into(),
210 field.to_string().as_str().into(),
211 ])?
212 }
213 _ => unimplemented!(),
214 };
215
216 assert!(proof.index == field, "the field leaf index is incorrect");
217
218 let proof = proof.branch.iter().map(|n| n.0.into()).collect::<Vec<_>>();
219
220 let anchor = BeaconAnchor::new(proof, id);
221
222 Ok(anchor)
223 }
224}
225
226#[async_trait]
227impl<P: Provider<AnyNetwork>> AnchorBuilder for BeaconAnchorBuilder<P, Eip4788BeaconAnchor> {
228 async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
229 let header = self.header_anchor_builder.get_header(block_id).await?;
230 let anchor =
231 self.build_beacon_anchor_with_header(&header, BeaconBlockField::BlockHash).await?;
232
233 Ok(Anchor::Eip4788(anchor))
234 }
235}
236
237#[async_trait]
238impl<P: Provider<AnyNetwork>> AnchorBuilder for BeaconAnchorBuilder<P, ConsensusBeaconAnchor> {
239 async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
240 let header = self.header_anchor_builder.get_header(block_id).await?;
241 let anchor =
242 self.build_beacon_anchor_with_header(&header, BeaconBlockField::BlockHash).await?;
243
244 Ok(Anchor::Consensus(anchor))
245 }
246}
247
248impl<P: Debug, K: Debug> Debug for BeaconAnchorBuilder<P, K> {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 f.debug_struct("BeaconAnchorBuilder")
251 .field("header_anchor_builder", &self.header_anchor_builder)
252 .finish()
253 }
254}
255
256#[derive(Debug)]
258pub struct ChainedBeaconAnchorBuilder<P> {
259 beacon_anchor_builder: BeaconAnchorBuilder<P, Eip4788BeaconAnchor>,
260 reference: BlockId,
262}
263
264impl<P> ChainedBeaconAnchorBuilder<P> {
265 pub fn new(
266 beacon_anchor_builder: BeaconAnchorBuilder<P, Eip4788BeaconAnchor>,
267 reference: BlockId,
268 ) -> Self {
269 Self { beacon_anchor_builder, reference }
270 }
271}
272
273impl<P: Provider<AnyNetwork>> ChainedBeaconAnchorBuilder<P> {
274 async fn get_eip_4788_timestamp(
276 &self,
277 timestamp: U256,
278 block_hash: B256,
279 ) -> Result<U256, HostError> {
280 let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH;
281 let result = self
282 .beacon_anchor_builder
283 .header_anchor_builder
284 .provider
285 .get_storage_at(BEACON_ROOTS_ADDRESS, timestamp_idx)
286 .block_id(BlockId::Hash(block_hash.into()))
287 .await?;
288
289 Ok(result)
290 }
291
292 async fn retrieve_state(
294 &self,
295 timestamp: U256,
296 block_hash: B256,
297 ) -> Result<EthereumState, HostError> {
298 let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH;
300 let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH;
301
302 let provider = &self.beacon_anchor_builder.header_anchor_builder.provider;
303
304 let proof = provider
305 .get_proof(BEACON_ROOTS_ADDRESS, vec![timestamp_idx.into(), root_idx.into()])
306 .block_id(BlockId::Hash(block_hash.into()))
307 .await?;
308
309 let state = EthereumState::from_account_proof(proof)?;
310
311 Ok(state)
312 }
313}
314
315#[async_trait]
316impl<P: Provider<AnyNetwork>> AnchorBuilder for ChainedBeaconAnchorBuilder<P> {
317 async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
319 let execution_header =
320 self.beacon_anchor_builder.header_anchor_builder.get_header(block_id).await?;
321 let reference_header =
322 self.beacon_anchor_builder.header_anchor_builder.get_header(self.reference).await?;
323 assert!(
324 execution_header.number < reference_header.number,
325 "The execution block must be an ancestor of the reference block"
326 );
327
328 let execution_anchor = self
330 .beacon_anchor_builder
331 .build_beacon_anchor_with_header(&execution_header, BeaconBlockField::BlockHash)
332 .await?;
333 let mut current_anchor = Some(
335 self.beacon_anchor_builder
336 .build_beacon_anchor_with_header(&reference_header, BeaconBlockField::StateRoot)
337 .await?
338 .into(),
339 );
340 let mut current_state_block_hash = reference_header.seal();
341 let mut state_anchors: Vec<BeaconStateAnchor> = vec![];
342
343 loop {
345 let timestamp = self
346 .get_eip_4788_timestamp(
347 U256::from(execution_anchor.id().as_timestamp().unwrap()),
348 current_state_block_hash,
349 )
350 .await?;
351 let state = self.retrieve_state(timestamp, current_state_block_hash).await?;
353 let parent_beacon_root = get_beacon_root_from_state(&state, timestamp);
354
355 state_anchors.insert(0, BeaconStateAnchor::new(state, current_anchor.take().unwrap()));
356
357 if timestamp == U256::from(execution_anchor.id().as_timestamp().unwrap()) {
359 assert!(
360 parent_beacon_root == execution_anchor.beacon_root(),
361 "failed to validate final beacon anchor"
362 );
363 break;
364 }
365
366 current_state_block_hash = self
367 .beacon_anchor_builder
368 .client
369 .get_execution_payload_block_hash(parent_beacon_root.to_string())
370 .await?;
371
372 let _ = current_anchor.replace(
374 self.beacon_anchor_builder
375 .build_beacon_anchor(
376 parent_beacon_root,
377 BeaconAnchorId::Timestamp(timestamp.to()),
378 BeaconBlockField::StateRoot,
379 )
380 .await?,
381 );
382 }
383
384 Ok(Anchor::ChainedEip4788(ChainedBeaconAnchor::new(execution_anchor, state_anchors)))
385 }
386}
387
388fn verify_merkle_root(
390 block_hash: B256,
391 proof: &[B256],
392 generalized_index: usize,
393 beacon_root: B256,
394) -> bool {
395 rebuild_merkle_root(block_hash, generalized_index, proof) == beacon_root
396}