sp1_cc_host_executor/
anchor_builder.rs

1use std::{
2    fmt::{Debug, Display},
3    marker::PhantomData,
4};
5
6use alloy_consensus::{Header, Sealed};
7use alloy_eips::{eip4788::BEACON_ROOTS_ADDRESS, BlockId};
8use alloy_primitives::{B256, U256};
9use alloy_provider::{network::AnyNetwork, Provider};
10use async_trait::async_trait;
11use ethereum_consensus::ssz::prelude::Prove;
12use rsp_mpt::EthereumState;
13use sp1_cc_client_executor::{
14    get_beacon_root_from_state, rebuild_merkle_root, Anchor, BeaconAnchor, BeaconAnchorId,
15    BeaconStateAnchor, BeaconWithHeaderAnchor, ChainedBeaconAnchor, BLOCK_HASH_LEAF_INDEX,
16    HISTORY_BUFFER_LENGTH, STATE_ROOT_LEAF_INDEX,
17};
18use url::Url;
19
20use crate::{
21    beacon::{BeaconClient, SignedBeaconBlock},
22    HostError,
23};
24
25/// Abstracts [`Anchor`] creation.
26#[async_trait]
27pub trait AnchorBuilder {
28    async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError>;
29}
30
31/// A field identifier for beacon block components that can be verified via Merkle proofs.
32///
33/// This enum specifies which field of a beacon block should be used as the leaf value
34/// in Merkle proof verification. Different anchor types require verification of different
35/// beacon block fields to establish the cryptographic link between execution and consensus layers.
36#[derive(Debug, Clone, Copy)]
37pub enum BeaconBlockField {
38    BlockHash,
39    StateRoot,
40}
41
42impl Display for BeaconBlockField {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            BeaconBlockField::BlockHash => write!(f, "block_hash"),
46            BeaconBlockField::StateRoot => write!(f, "state_root"),
47        }
48    }
49}
50
51impl PartialEq<BeaconBlockField> for usize {
52    fn eq(&self, other: &BeaconBlockField) -> bool {
53        let other = usize::from(other);
54
55        *self == other
56    }
57}
58
59impl From<&BeaconBlockField> for usize {
60    fn from(value: &BeaconBlockField) -> Self {
61        match value {
62            BeaconBlockField::BlockHash => BLOCK_HASH_LEAF_INDEX,
63            BeaconBlockField::StateRoot => STATE_ROOT_LEAF_INDEX,
64        }
65    }
66}
67
68/// Trait for different beacon anchor strategies.
69#[async_trait]
70pub trait BeaconAnchorKind: Sized {
71    async fn build_beacon_anchor_from_header<P: Provider<AnyNetwork>>(
72        header: &Sealed<Header>,
73        field: BeaconBlockField,
74        beacon_anchor_builder: &BeaconAnchorBuilder<P, Self>,
75    ) -> Result<(B256, BeaconAnchor), HostError>;
76}
77
78/// Marker type for EIP-4788 beacon anchor strategy.
79#[derive(Debug)]
80pub struct Eip4788BeaconAnchor;
81
82#[async_trait]
83impl BeaconAnchorKind for Eip4788BeaconAnchor {
84    async fn build_beacon_anchor_from_header<P: Provider<AnyNetwork>>(
85        header: &Sealed<Header>,
86        field: BeaconBlockField,
87        beacon_anchor_builder: &BeaconAnchorBuilder<P, Self>,
88    ) -> Result<(B256, BeaconAnchor), HostError> {
89        let child_header =
90            beacon_anchor_builder.header_anchor_builder.get_header(header.number + 1).await?;
91        assert_eq!(child_header.parent_hash, header.seal());
92
93        let beacon_root = child_header
94            .parent_beacon_block_root
95            .ok_or_else(|| HostError::ParentBeaconBlockRootMissing)?;
96
97        let anchor = beacon_anchor_builder
98            .build_beacon_anchor(
99                beacon_root,
100                BeaconAnchorId::Timestamp(child_header.timestamp),
101                field,
102            )
103            .await?;
104
105        Ok((beacon_root, anchor))
106    }
107}
108
109/// Marker type for consensus beacon anchor strategy.
110#[derive(Debug)]
111pub struct ConsensusBeaconAnchor;
112
113#[async_trait]
114impl BeaconAnchorKind for ConsensusBeaconAnchor {
115    async fn build_beacon_anchor_from_header<P: Provider<AnyNetwork>>(
116        header: &Sealed<Header>,
117        field: BeaconBlockField,
118        beacon_anchor_builder: &BeaconAnchorBuilder<P, Self>,
119    ) -> Result<(B256, BeaconAnchor), HostError> {
120        let parent_root = header
121            .parent_beacon_block_root
122            .ok_or_else(|| HostError::ParentBeaconBlockRootMissing)?;
123
124        let (beacon_root, beacon_header) = beacon_anchor_builder
125            .client
126            .get_header_from_parent_root(parent_root.to_string())
127            .await?;
128
129        let anchor = beacon_anchor_builder
130            .build_beacon_anchor(
131                beacon_root,
132                BeaconAnchorId::Slot(beacon_header.message.slot),
133                field,
134            )
135            .await?;
136
137        Ok((beacon_root, anchor))
138    }
139}
140
141/// A builder for [`HeaderAnchor`].
142///
143/// [`HeaderAnchor`]: sp1_cc_client_executor::HeaderAnchor
144#[derive(Debug)]
145pub struct HeaderAnchorBuilder<P> {
146    provider: P,
147}
148
149impl<P> HeaderAnchorBuilder<P> {
150    pub fn new(provider: P) -> Self {
151        Self { provider }
152    }
153}
154
155impl<P: Provider<AnyNetwork>> HeaderAnchorBuilder<P> {
156    pub async fn get_header<B: Into<BlockId>>(
157        &self,
158        block_id: B,
159    ) -> Result<Sealed<Header>, HostError> {
160        let block_id = block_id.into();
161        let block = self
162            .provider
163            .get_block(block_id)
164            .await?
165            .ok_or_else(|| HostError::BlockNotFoundError(block_id))?;
166
167        let header = block
168            .header
169            .inner
170            .clone()
171            .try_into_header()
172            .map_err(|_| HostError::HeaderConversionError(block.inner.header.number))?;
173
174        Ok(Sealed::new(header))
175    }
176}
177
178#[async_trait]
179impl<P: Provider<AnyNetwork>> AnchorBuilder for HeaderAnchorBuilder<P> {
180    async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
181        let header = self.get_header(block_id).await?;
182
183        Ok(header.into_inner().into())
184    }
185}
186
187/// A builder for [`BeaconAnchor`].
188pub struct BeaconAnchorBuilder<P, K> {
189    header_anchor_builder: HeaderAnchorBuilder<P>,
190    client: BeaconClient,
191    phantom: PhantomData<K>,
192}
193
194impl<P> BeaconAnchorBuilder<P, Eip4788BeaconAnchor> {
195    /// Creates a new EIP-4788 beacon anchor builder.
196    pub fn new(header_anchor_builder: HeaderAnchorBuilder<P>, cl_rpc_url: Url) -> Self {
197        Self { header_anchor_builder, client: BeaconClient::new(cl_rpc_url), phantom: PhantomData }
198    }
199
200    /// Converts this EIP-4788 beacon anchor builder to a consensus beacon anchor builder.
201    pub fn into_consensus(self) -> BeaconAnchorBuilder<P, ConsensusBeaconAnchor> {
202        BeaconAnchorBuilder {
203            header_anchor_builder: self.header_anchor_builder,
204            client: self.client,
205            phantom: PhantomData,
206        }
207    }
208}
209
210impl<P: Provider<AnyNetwork>, K: BeaconAnchorKind> BeaconAnchorBuilder<P, K> {
211    /// Builds a beacon anchor with a header for the specified field.
212    pub async fn build_beacon_anchor_with_header(
213        &self,
214        header: &Sealed<Header>,
215        field: BeaconBlockField,
216    ) -> Result<BeaconWithHeaderAnchor, HostError> {
217        let (beacon_root, anchor) = K::build_beacon_anchor_from_header(header, field, self).await?;
218
219        if matches!(field, BeaconBlockField::BlockHash) {
220            assert!(
221                verify_merkle_root(header.seal(), anchor.proof(), usize::from(&field), beacon_root),
222                "the proof verification fail, field: {field}",
223            );
224        }
225
226        Ok(BeaconWithHeaderAnchor::new(header.clone_inner(), anchor))
227    }
228
229    /// Builds a beacon anchor for the given beacon root and field.
230    pub async fn build_beacon_anchor(
231        &self,
232        beacon_root: B256,
233        id: BeaconAnchorId,
234        field: BeaconBlockField,
235    ) -> Result<BeaconAnchor, HostError> {
236        let signed_beacon_block = self.client.get_block(beacon_root.to_string()).await?;
237
238        let (proof, _) = match signed_beacon_block {
239            SignedBeaconBlock::Deneb(signed_beacon_block) => {
240                signed_beacon_block.message.prove(&[
241                    "body".into(),
242                    "execution_payload".into(),
243                    field.to_string().as_str().into(),
244                ])?
245            }
246            SignedBeaconBlock::Electra(signed_beacon_block) => {
247                signed_beacon_block.message.prove(&[
248                    "body".into(),
249                    "execution_payload".into(),
250                    field.to_string().as_str().into(),
251                ])?
252            }
253            _ => unimplemented!(),
254        };
255
256        assert!(proof.index == field, "the field leaf index is incorrect");
257
258        let proof = proof.branch.iter().map(|n| n.0.into()).collect::<Vec<_>>();
259
260        let anchor = BeaconAnchor::new(proof, id);
261
262        Ok(anchor)
263    }
264}
265
266#[async_trait]
267impl<P: Provider<AnyNetwork>> AnchorBuilder for BeaconAnchorBuilder<P, Eip4788BeaconAnchor> {
268    async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
269        let header = self.header_anchor_builder.get_header(block_id).await?;
270        let anchor =
271            self.build_beacon_anchor_with_header(&header, BeaconBlockField::BlockHash).await?;
272
273        Ok(Anchor::Eip4788(anchor))
274    }
275}
276
277#[async_trait]
278impl<P: Provider<AnyNetwork>> AnchorBuilder for BeaconAnchorBuilder<P, ConsensusBeaconAnchor> {
279    async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
280        let header = self.header_anchor_builder.get_header(block_id).await?;
281        let anchor =
282            self.build_beacon_anchor_with_header(&header, BeaconBlockField::BlockHash).await?;
283
284        Ok(Anchor::Consensus(anchor))
285    }
286}
287
288impl<P: Debug, K: Debug> Debug for BeaconAnchorBuilder<P, K> {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        f.debug_struct("BeaconAnchorBuilder")
291            .field("header_anchor_builder", &self.header_anchor_builder)
292            .finish()
293    }
294}
295
296/// A builder for [`ChainedBeaconAnchor`].
297#[derive(Debug)]
298pub struct ChainedBeaconAnchorBuilder<P> {
299    beacon_anchor_builder: BeaconAnchorBuilder<P, Eip4788BeaconAnchor>,
300    /// The reference is a successor of the execution block.
301    reference: BlockId,
302}
303
304impl<P> ChainedBeaconAnchorBuilder<P> {
305    pub fn new(
306        beacon_anchor_builder: BeaconAnchorBuilder<P, Eip4788BeaconAnchor>,
307        reference: BlockId,
308    ) -> Self {
309        Self { beacon_anchor_builder, reference }
310    }
311}
312
313impl<P: Provider<AnyNetwork>> ChainedBeaconAnchorBuilder<P> {
314    /// Retrieves the timestamp stored in the EIP-4788 beacon roots contract for a given timestamp
315    /// and block.
316    async fn get_eip_4788_timestamp(
317        &self,
318        timestamp: U256,
319        block_hash: B256,
320    ) -> Result<U256, HostError> {
321        let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH;
322        let result = self
323            .beacon_anchor_builder
324            .header_anchor_builder
325            .provider
326            .get_storage_at(BEACON_ROOTS_ADDRESS, timestamp_idx)
327            .block_id(BlockId::Hash(block_hash.into()))
328            .await?;
329
330        Ok(result)
331    }
332
333    /// Retrieves the EIP-4788 storage proof for the beacon root contract at the given timestamp and
334    /// block.
335    async fn retrieve_state(
336        &self,
337        timestamp: U256,
338        block_hash: B256,
339    ) -> Result<EthereumState, HostError> {
340        // Compute the indexes of the two storage slots that will be queried
341        let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH;
342        let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH;
343
344        let provider = &self.beacon_anchor_builder.header_anchor_builder.provider;
345
346        let proof = provider
347            .get_proof(BEACON_ROOTS_ADDRESS, vec![timestamp_idx.into(), root_idx.into()])
348            .block_id(BlockId::Hash(block_hash.into()))
349            .await?;
350
351        let state = EthereumState::from_account_proof(proof)?;
352
353        Ok(state)
354    }
355}
356
357#[async_trait]
358impl<P: Provider<AnyNetwork>> AnchorBuilder for ChainedBeaconAnchorBuilder<P> {
359    /// Builds a chained beacon anchor for the given block ID.
360    async fn build<B: Into<BlockId> + Send>(&self, block_id: B) -> Result<Anchor, HostError> {
361        let execution_header =
362            self.beacon_anchor_builder.header_anchor_builder.get_header(block_id).await?;
363        let reference_header =
364            self.beacon_anchor_builder.header_anchor_builder.get_header(self.reference).await?;
365        assert!(
366            execution_header.number < reference_header.number,
367            "The execution block must be an ancestor of the reference block"
368        );
369
370        // Build an anchor for the execution block containing the beacon root we need to verify
371        let execution_anchor = self
372            .beacon_anchor_builder
373            .build_beacon_anchor_with_header(&execution_header, BeaconBlockField::BlockHash)
374            .await?;
375        // Build an anchor for the reference block
376        let mut current_anchor = Some(
377            self.beacon_anchor_builder
378                .build_beacon_anchor_with_header(&reference_header, BeaconBlockField::StateRoot)
379                .await?
380                .into(),
381        );
382        let mut current_state_block_hash = reference_header.seal();
383        let mut state_anchors: Vec<BeaconStateAnchor> = vec![];
384
385        // Loop backwards until we reach the execution block beacon root
386        loop {
387            let timestamp = self
388                .get_eip_4788_timestamp(
389                    U256::from(execution_anchor.id().as_timestamp().unwrap()),
390                    current_state_block_hash,
391                )
392                .await?;
393            // Prefetch the beacon roots contract call for timestamp
394            let state = self.retrieve_state(timestamp, current_state_block_hash).await?;
395            let parent_beacon_root = get_beacon_root_from_state(&state, timestamp);
396
397            state_anchors.insert(0, BeaconStateAnchor::new(state, current_anchor.take().unwrap()));
398
399            // Check if we've reached the execution block beacon root
400            if timestamp == U256::from(execution_anchor.id().as_timestamp().unwrap()) {
401                assert!(
402                    parent_beacon_root == execution_anchor.beacon_root(),
403                    "failed to validate final beacon anchor"
404                );
405                break;
406            }
407
408            current_state_block_hash = self
409                .beacon_anchor_builder
410                .client
411                .get_execution_payload_block_hash(parent_beacon_root.to_string())
412                .await?;
413
414            // Update the current anchor with the new beacon root
415            let _ = current_anchor.replace(
416                self.beacon_anchor_builder
417                    .build_beacon_anchor(
418                        parent_beacon_root,
419                        BeaconAnchorId::Timestamp(timestamp.to()),
420                        BeaconBlockField::StateRoot,
421                    )
422                    .await?,
423            );
424        }
425
426        Ok(Anchor::ChainedEip4788(ChainedBeaconAnchor::new(execution_anchor, state_anchors)))
427    }
428}
429
430/// Verifies a Merkle proof by rebuilding the root and comparing it to the expected beacon root.
431fn verify_merkle_root(
432    block_hash: B256,
433    proof: &[B256],
434    generalized_index: usize,
435    beacon_root: B256,
436) -> bool {
437    rebuild_merkle_root(block_hash, generalized_index, proof) == beacon_root
438}