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::{bail, 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, .. } => output.data().clone(),
69            ExecutionResult::Revert { output, .. } => bail!("Execution reverted: {output}"),
70            ExecutionResult::Halt { reason, .. } => bail!("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, .. } => output.data().clone(),
85            ExecutionResult::Revert { output, .. } => bail!("Execution reverted: {output}"),
86            ExecutionResult::Halt { reason, .. } => bail!("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, .. } => output.data().clone(),
102            ExecutionResult::Revert { output, .. } => bail!("Execution reverted: {output}"),
103            ExecutionResult::Halt { reason, .. } => bail!("Execution halted: {reason:?}"),
104        };
105
106        Ok(output_bytes)
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            bytecodes: self.rpc_db.get_bytecodes(),
184            receipts: self.receipts,
185        })
186    }
187}
188
189/// Converts an [`AnyReceiptEnvelope`] to a [`ReceiptEnvelope`] by encoding and decoding.
190fn convert_receipt_envelope(
191    any_receipt_envelope: AnyReceiptEnvelope<RpcLog>,
192) -> Result<ReceiptEnvelope, Eip2718Error> {
193    let any_receipt_envelope = AnyReceiptEnvelope {
194        inner: any_receipt_envelope.inner.map_logs(|l| l.inner),
195        r#type: any_receipt_envelope.r#type,
196    };
197
198    let mut buf = vec![];
199
200    any_receipt_envelope.encode_2718(&mut buf);
201
202    ReceiptEnvelope::decode_2718(&mut buf.as_slice())
203}
204
205#[cfg(test)]
206mod tests {
207    use reth_primitives::EthPrimitives;
208    use sp1_cc_client_executor::io::EvmSketchInput;
209
210    use crate::EvmSketch;
211
212    // Function that requires T to be `Sync`
213    fn assert_sync<T: Sync>() {}
214
215    #[test]
216    fn test_sync() {
217        assert_sync::<EvmSketch<(), EthPrimitives>>();
218        assert_sync::<EvmSketchInput>();
219    }
220}