sp1_cc_host_executor/
sketch.rs

1use std::{collections::BTreeSet, marker::PhantomData};
2
3use alloy_consensus::ReceiptEnvelope;
4use alloy_eips::{eip2718::Eip2718Error, Decodable2718, Encodable2718};
5use alloy_primitives::{Address, Bytes, B256, U256};
6use alloy_provider::{network::AnyNetwork, Provider};
7use alloy_rpc_types::{AnyReceiptEnvelope, Filter, Log as RpcLog};
8use alloy_sol_types::SolCall;
9use eyre::eyre;
10use reth_primitives::EthPrimitives;
11use revm::{context::result::ExecutionResult, database::CacheDB};
12use rsp_mpt::EthereumState;
13use rsp_primitives::{account_proof::eip1186_proof_to_account_proof, genesis::Genesis};
14use rsp_rpc_db::RpcDb;
15use sp1_cc_client_executor::{
16    io::{EvmSketchInput, Primitives},
17    Anchor, ContractInput,
18};
19
20use crate::{EvmSketchBuilder, HostError};
21
22/// [`EvmSketch`] is used to prefetch all the data required to execute a block and query logs in the
23/// zkVM.
24#[derive(Debug)]
25pub struct EvmSketch<P, PT> {
26    /// The genesis block specification.
27    pub genesis: Genesis,
28    /// The anchor to execute our view functions on.
29    pub anchor: Anchor,
30    /// The [`RpcDb`] used to back the EVM.
31    pub rpc_db: RpcDb<P, AnyNetwork>,
32    /// The receipts used to retrieve event logs.
33    pub receipts: Option<Vec<ReceiptEnvelope>>,
34    /// The provider used to fetch data.
35    pub provider: P,
36
37    pub phantom: PhantomData<PT>,
38}
39
40impl EvmSketch<(), EthPrimitives> {
41    pub fn builder() -> EvmSketchBuilder<(), EthPrimitives, ()> {
42        EvmSketchBuilder::default()
43    }
44}
45
46impl<P, PT> EvmSketch<P, PT>
47where
48    P: Provider<AnyNetwork> + Clone,
49    PT: Primitives,
50{
51    /// Executes a smart contract call.
52    ///
53    /// The accessed accounts and storages are recorded, and included in a [`EvmSketchInput`]
54    /// when [`Self::finalize`] is called.
55    pub async fn call<C: SolCall>(
56        &self,
57        contract_address: Address,
58        caller_address: Address,
59        calldata: C,
60    ) -> eyre::Result<C::Return> {
61        let cache_db = CacheDB::new(&self.rpc_db);
62        let chain_spec = PT::build_spec(&self.genesis)?;
63        let input = ContractInput::new_call(contract_address, caller_address, calldata);
64        let output = PT::transact(&input, cache_db, self.anchor.header(), U256::ZERO, chain_spec)
65            .map_err(|err| eyre!(err))?;
66
67        let output_bytes = match output.result {
68            ExecutionResult::Success { output, .. } => Ok(output.data().clone()),
69            ExecutionResult::Revert { output, .. } => Ok(output),
70            ExecutionResult::Halt { reason, .. } => Err(eyre!("Execution halted: {reason:?}")),
71        }?;
72
73        Ok(C::abi_decode_returns(&output_bytes)?)
74    }
75
76    /// Executes a smart contract call, using the provided [`ContractInput`].
77    pub async fn call_raw(&self, input: &ContractInput) -> eyre::Result<Bytes> {
78        let cache_db = CacheDB::new(&self.rpc_db);
79        let chain_spec = PT::build_spec(&self.genesis)?;
80        let output = PT::transact(input, cache_db, self.anchor.header(), U256::ZERO, chain_spec)
81            .map_err(|err| eyre!(err))?;
82
83        let output_bytes = match output.result {
84            ExecutionResult::Success { output, .. } => Ok(output.data().clone()),
85            ExecutionResult::Revert { output, .. } => Ok(output),
86            ExecutionResult::Halt { reason, .. } => Err(eyre!("Execution halted: {reason:?}")),
87        }?;
88
89        Ok(output_bytes)
90    }
91
92    /// Executes a smart contract creation.
93    pub async fn create(&self, caller_address: Address, calldata: Bytes) -> eyre::Result<Bytes> {
94        let cache_db = CacheDB::new(&self.rpc_db);
95        let chain_spec = PT::build_spec(&self.genesis)?;
96        let input = ContractInput::new_create(caller_address, calldata);
97        let output = PT::transact(&input, cache_db, self.anchor.header(), U256::ZERO, chain_spec)
98            .map_err(|err| eyre!(err))?;
99
100        let output_bytes = match output.result {
101            ExecutionResult::Success { output, .. } => Ok(output.data().clone()),
102            ExecutionResult::Revert { output, .. } => Ok(output),
103            ExecutionResult::Halt { reason, .. } => Err(eyre!("Execution halted: {reason:?}")),
104        }?;
105
106        Ok(output_bytes.clone())
107    }
108
109    /// Prefetch the logs matching the provided `filter`, allowing them to be retrieved in the
110    /// client using [`get_logs`].
111    ///
112    /// [`get_logs`]: sp1_cc_client_executor::ClientExecutor::get_logs
113    pub async fn get_logs(&mut self, filter: &Filter) -> Result<Vec<RpcLog>, HostError> {
114        let logs = self.provider.get_logs(filter).await?;
115
116        if !logs.is_empty() && self.receipts.is_none() {
117            let receipts = self
118                .provider
119                .get_block_receipts(self.anchor.header().number.into())
120                .await?
121                .unwrap_or_default()
122                .into_iter()
123                .map(|r| convert_receipt_envelope(r.inner.inner))
124                .collect::<Result<_, _>>()?;
125
126            self.receipts = Some(receipts);
127        }
128
129        Ok(logs)
130    }
131
132    /// Returns the cumulative [`EvmSketchInput`] after executing some smart contracts.
133    pub async fn finalize(self) -> Result<EvmSketchInput, HostError> {
134        let block_number = self.anchor.header().number;
135
136        // For every account touched, fetch the storage proofs for all the slots touched.
137        let state_requests = self.rpc_db.get_state_requests();
138        tracing::info!("fetching storage proofs");
139        let mut storage_proofs = Vec::new();
140
141        for (address, used_keys) in state_requests.iter() {
142            let keys = used_keys
143                .iter()
144                .map(|key| B256::from(*key))
145                .collect::<BTreeSet<_>>()
146                .into_iter()
147                .collect::<Vec<_>>();
148
149            let storage_proof =
150                self.provider.get_proof(*address, keys).block_id(block_number.into()).await?;
151            storage_proofs.push(eip1186_proof_to_account_proof(storage_proof));
152        }
153
154        let storage_proofs_by_address =
155            storage_proofs.iter().map(|item| (item.address, item.clone())).collect();
156        let state = EthereumState::from_proofs(
157            self.anchor.header().state_root,
158            &storage_proofs_by_address,
159        )?;
160
161        // Fetch the parent headers needed to constrain the BLOCKHASH opcode.
162        let oldest_ancestor = *self.rpc_db.oldest_ancestor.read().unwrap();
163        let mut ancestor_headers = vec![];
164        tracing::info!("fetching {} ancestor headers", block_number - oldest_ancestor);
165        for height in (oldest_ancestor..=(block_number - 1)).rev() {
166            let block = self.provider.get_block_by_number(height.into()).full().await?.unwrap();
167            ancestor_headers.push(
168                block
169                    .inner
170                    .header
171                    .inner
172                    .clone()
173                    .try_into_header()
174                    .map_err(|h| HostError::HeaderConversionError(h.number))?,
175            );
176        }
177
178        Ok(EvmSketchInput {
179            anchor: self.anchor,
180            genesis: self.genesis,
181            ancestor_headers,
182            state,
183            state_requests,
184            bytecodes: self.rpc_db.get_bytecodes(),
185            receipts: self.receipts,
186        })
187    }
188}
189
190/// Converts an [`AnyReceiptEnvelope`] to a [`ReceiptEnvelope`] by encoding and decoding.
191fn convert_receipt_envelope(
192    any_receipt_envelope: AnyReceiptEnvelope<RpcLog>,
193) -> Result<ReceiptEnvelope, Eip2718Error> {
194    let any_receipt_envelope = AnyReceiptEnvelope {
195        inner: any_receipt_envelope.inner.map_logs(|l| l.inner),
196        r#type: any_receipt_envelope.r#type,
197    };
198
199    let mut buf = vec![];
200
201    any_receipt_envelope.encode_2718(&mut buf);
202
203    ReceiptEnvelope::decode_2718(&mut buf.as_slice())
204}
205
206#[cfg(test)]
207mod tests {
208    use reth_primitives::EthPrimitives;
209    use sp1_cc_client_executor::io::EvmSketchInput;
210
211    use crate::EvmSketch;
212
213    // Function that requires T to be `Sync`
214    fn assert_sync<T: Sync>() {}
215
216    #[test]
217    fn test_sync() {
218        assert_sync::<EvmSketch<(), EthPrimitives>>();
219        assert_sync::<EvmSketchInput>();
220    }
221}