sp1_cc_client_executor/
io.rs

1//! EVM sketch input structures and implementations.
2//!
3//! This module provides the [`EvmSketchInput`] struct, which contains all the necessary
4//! information for executing Ethereum Virtual Machine (EVM) contracts in SP1. Instead of
5//! passing the entire blockchain state, it includes only the required state roots, merkle
6//! proofs, and specific storage slots that were accessed or modified during execution.
7//!
8//! The main purpose is to optimize contract execution by providing a minimal witness
9//! that contains just the data needed to prove correct execution.
10
11use std::{fmt::Debug, iter::once, sync::Arc};
12
13use alloy_consensus::ReceiptEnvelope;
14use alloy_evm::{Database, Evm};
15use reth_chainspec::{ChainSpec, EthChainSpec};
16use reth_consensus::{ConsensusError, HeaderValidator};
17use reth_ethereum_consensus::EthBeaconConsensus;
18use reth_evm::{ConfigureEvm, EthEvm, EvmEnv};
19use reth_evm_ethereum::EthEvmConfig;
20use reth_primitives::{EthPrimitives, Header, NodePrimitives, SealedHeader};
21use revm::{
22    context::result::{HaltReason, ResultAndState},
23    inspector::NoOpInspector,
24    state::Bytecode,
25    Context, MainBuilder, MainContext,
26};
27use revm_primitives::{Address, B256, U256};
28use rsp_client_executor::{error::ClientError, io::WitnessInput};
29use rsp_mpt::EthereumState;
30use rsp_primitives::genesis::Genesis;
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33
34use crate::{Anchor, ContractInput};
35
36/// Information about how the contract executions accessed state, which is needed to execute the
37/// contract in SP1.
38///
39/// Instead of passing in the entire state, only the state roots and merkle proofs
40/// for the storage slots that were modified and accessed are passed in.
41#[serde_as]
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
43pub struct EvmSketchInput {
44    /// The current block anchor.
45    pub anchor: Anchor,
46    /// The genesis block specification.
47    pub genesis: Genesis,
48    /// The previous block headers starting from the most recent. These are used for calls to the
49    /// blockhash opcode.
50    #[serde_as(as = "Vec<alloy_consensus::serde_bincode_compat::Header>")]
51    pub ancestor_headers: Vec<Header>,
52    /// Current block's Ethereum state.
53    pub state: EthereumState,
54    /// Account bytecodes.
55    pub bytecodes: Vec<Bytecode>,
56    /// Receipts.
57    #[serde_as(as = "Option<Vec<alloy_consensus::serde_bincode_compat::ReceiptEnvelope>>")]
58    pub receipts: Option<Vec<ReceiptEnvelope>>,
59}
60
61impl WitnessInput for EvmSketchInput {
62    #[inline(always)]
63    fn state(&self) -> &EthereumState {
64        &self.state
65    }
66
67    #[inline(always)]
68    fn state_anchor(&self) -> B256 {
69        self.anchor.header().state_root
70    }
71
72    #[inline(always)]
73    fn state_requests(&self) -> impl Iterator<Item = (&Address, &Vec<U256>)> {
74        // Workaround for https://github.com/rust-lang/rust/issues/36375
75        if true {
76            unimplemented!()
77        } else {
78            std::iter::empty()
79        }
80    }
81
82    #[inline(always)]
83    fn bytecodes(&self) -> impl Iterator<Item = &Bytecode> {
84        self.bytecodes.iter()
85    }
86
87    #[inline(always)]
88    fn sealed_headers(&self) -> impl Iterator<Item = SealedHeader> {
89        once(SealedHeader::seal_slow(self.anchor.header().clone()))
90            .chain(self.ancestor_headers.iter().map(|h| SealedHeader::seal_slow(h.clone())))
91    }
92}
93
94pub trait Primitives: NodePrimitives {
95    type ChainSpec: EthChainSpec + Debug;
96    type HaltReason: Debug;
97
98    fn build_spec(genesis: &Genesis) -> Result<Arc<Self::ChainSpec>, ClientError>;
99
100    fn validate_header(
101        header: &SealedHeader,
102        chain_spec: Arc<Self::ChainSpec>,
103    ) -> Result<(), ConsensusError>;
104
105    fn transact<DB>(
106        input: &ContractInput,
107        db: DB,
108        header: &Header,
109        difficulty: U256,
110        chain_spec: Arc<Self::ChainSpec>,
111    ) -> Result<ResultAndState<Self::HaltReason>, String>
112    where
113        DB: Database;
114
115    fn active_fork_name(chain_spec: &Self::ChainSpec, header: &Header) -> String;
116}
117
118impl Primitives for EthPrimitives {
119    type ChainSpec = ChainSpec;
120    type HaltReason = HaltReason;
121
122    fn build_spec(genesis: &Genesis) -> Result<Arc<Self::ChainSpec>, ClientError> {
123        Ok(Arc::new(ChainSpec::try_from(genesis).unwrap()))
124    }
125
126    fn validate_header(
127        header: &SealedHeader,
128        chain_spec: Arc<Self::ChainSpec>,
129    ) -> Result<(), ConsensusError> {
130        let validator = EthBeaconConsensus::new(chain_spec);
131        validator.validate_header(header)
132    }
133
134    fn transact<DB: Database>(
135        input: &ContractInput,
136        db: DB,
137        header: &Header,
138        difficulty: U256,
139        chain_spec: Arc<Self::ChainSpec>,
140    ) -> Result<ResultAndState<Self::HaltReason>, String> {
141        let EvmEnv { mut cfg_env, mut block_env, .. } =
142            EthEvmConfig::new(chain_spec).evm_env(header);
143
144        // Set the base fee to 0 to enable 0 gas price transactions.
145        block_env.basefee = 0;
146        block_env.difficulty = difficulty;
147        cfg_env.disable_nonce_check = true;
148        cfg_env.disable_balance_check = true;
149
150        let evm = Context::mainnet()
151            .with_db(db)
152            .with_cfg(cfg_env)
153            .with_block(block_env)
154            .modify_tx_chained(|tx_env| {
155                tx_env.gas_limit = header.gas_limit;
156            })
157            .build_mainnet_with_inspector(NoOpInspector {});
158
159        let mut evm = EthEvm::new(evm, false);
160
161        evm.transact(input).map_err(|err| err.to_string())
162    }
163
164    fn active_fork_name(chain_spec: &Self::ChainSpec, header: &Header) -> String {
165        let spec = reth_evm_ethereum::revm_spec(chain_spec, header);
166
167        spec.to_string()
168    }
169}
170
171#[cfg(feature = "optimism")]
172impl Primitives for reth_optimism_primitives::OpPrimitives {
173    type ChainSpec = reth_optimism_chainspec::OpChainSpec;
174    type HaltReason = op_revm::OpHaltReason;
175
176    fn build_spec(genesis: &Genesis) -> Result<Arc<Self::ChainSpec>, ClientError> {
177        Ok(Arc::new(reth_optimism_chainspec::OpChainSpec::try_from(genesis).unwrap()))
178    }
179
180    fn validate_header(
181        header: &SealedHeader,
182        chain_spec: Arc<Self::ChainSpec>,
183    ) -> Result<(), ConsensusError> {
184        let validator = reth_optimism_consensus::OpBeaconConsensus::new(chain_spec);
185        validator.validate_header(header)
186    }
187
188    fn transact<DB: Database>(
189        input: &ContractInput,
190        db: DB,
191        header: &Header,
192        difficulty: U256,
193        chain_spec: Arc<Self::ChainSpec>,
194    ) -> Result<ResultAndState<Self::HaltReason>, String> {
195        use op_revm::{DefaultOp, OpBuilder};
196
197        let EvmEnv { mut cfg_env, mut block_env, .. } =
198            reth_optimism_evm::OpEvmConfig::optimism(chain_spec).evm_env(header);
199
200        // Set the base fee to 0 to enable 0 gas price transactions.
201        block_env.basefee = 0;
202        block_env.difficulty = difficulty;
203        cfg_env.disable_nonce_check = true;
204        cfg_env.disable_balance_check = true;
205
206        let evm = op_revm::OpContext::op()
207            .with_db(db)
208            .with_cfg(cfg_env)
209            .with_block(block_env)
210            .modify_tx_chained(|tx_env| {
211                tx_env.base.gas_limit = header.gas_limit;
212            })
213            .build_op_with_inspector(NoOpInspector {});
214
215        let mut evm = alloy_op_evm::OpEvm::new(evm, false);
216
217        evm.transact(input).map_err(|err| err.to_string())
218    }
219
220    fn active_fork_name(chain_spec: &Self::ChainSpec, header: &Header) -> String {
221        let spec = reth_optimism_evm::revm_spec(chain_spec, header);
222        let spec: &'static str = spec.into();
223
224        spec.to_string()
225    }
226}