sp1_cc_host_executor/
anchor_builder.rs

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/// Abstracts [`Anchor`] creation.
23#[async_trait]
24pub trait AnchorBuilder {
25    async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError>;
26}
27
28/// Trait for different beacon anchor strategies.
29#[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/// Marker type for EIP-4788 beacon anchor strategy.
39#[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/// Marker type for consensus beacon anchor strategy.
70#[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/// A builder for [`HeaderAnchor`].
102///
103/// [`HeaderAnchor`]: sp1_cc_client_executor::HeaderAnchor
104#[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
147/// A builder for [`BeaconAnchor`].
148pub 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    /// Creates a new EIP-4788 beacon anchor builder.
156    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    /// Converts this EIP-4788 beacon anchor builder to a consensus beacon anchor builder.
161    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    /// Builds a beacon anchor with a header for the specified field.
172    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    /// Builds a beacon anchor for the given beacon root and field.
190    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/// A builder for [`ChainedBeaconAnchor`].
257#[derive(Debug)]
258pub struct ChainedBeaconAnchorBuilder<P> {
259    beacon_anchor_builder: BeaconAnchorBuilder<P, Eip4788BeaconAnchor>,
260    /// The reference is a successor of the execution block.
261    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    /// Retrieves the timestamp stored in the EIP-4788 beacon roots contract for a given timestamp and block.
275    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    /// Retrieves the EIP-4788 storage proof for the beacon root contract at the given timestamp and block.
293    async fn retrieve_state(
294        &self,
295        timestamp: U256,
296        block_hash: B256,
297    ) -> Result<EthereumState, HostError> {
298        // Compute the indexes of the two storage slots that will be queried
299        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    /// Builds a chained beacon anchor for the given block ID.
318    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        // Build an anchor for the execution block containing the beacon root we need to verify
329        let execution_anchor = self
330            .beacon_anchor_builder
331            .build_beacon_anchor_with_header(&execution_header, BeaconBlockField::BlockHash)
332            .await?;
333        // Build an anchor for the reference block
334        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 backwards until we reach the execution block beacon root
344        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            // Prefetch the beacon roots contract call for timestamp
352            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            // Check if we've reached the execution block beacon root
358            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            // Update the current anchor with the new beacon root
373            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
388/// Verifies a Merkle proof by rebuilding the root and comparing it to the expected beacon root.
389fn 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}