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::{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 bytecodes(&self) -> impl Iterator<Item = &Bytecode> {
74        self.bytecodes.iter()
75    }
76
77    #[inline(always)]
78    fn sealed_headers(&self) -> impl Iterator<Item = SealedHeader> {
79        once(SealedHeader::seal_slow(self.anchor.header().clone()))
80            .chain(self.ancestor_headers.iter().map(|h| SealedHeader::seal_slow(h.clone())))
81    }
82}
83
84pub trait Primitives: NodePrimitives {
85    type ChainSpec: EthChainSpec + Debug;
86    type HaltReason: Debug;
87
88    fn build_spec(genesis: &Genesis) -> Result<Arc<Self::ChainSpec>, ClientError>;
89
90    fn validate_header(
91        header: &SealedHeader,
92        chain_spec: Arc<Self::ChainSpec>,
93    ) -> Result<(), ConsensusError>;
94
95    fn transact<DB>(
96        input: &ContractInput,
97        db: DB,
98        header: &Header,
99        difficulty: U256,
100        chain_spec: Arc<Self::ChainSpec>,
101    ) -> Result<ResultAndState<Self::HaltReason>, String>
102    where
103        DB: Database;
104
105    fn active_fork_name(chain_spec: &Self::ChainSpec, header: &Header) -> String;
106}
107
108impl Primitives for EthPrimitives {
109    type ChainSpec = ChainSpec;
110    type HaltReason = HaltReason;
111
112    fn build_spec(genesis: &Genesis) -> Result<Arc<Self::ChainSpec>, ClientError> {
113        Ok(Arc::new(ChainSpec::try_from(genesis).unwrap()))
114    }
115
116    fn validate_header(
117        header: &SealedHeader,
118        chain_spec: Arc<Self::ChainSpec>,
119    ) -> Result<(), ConsensusError> {
120        let validator = EthBeaconConsensus::new(chain_spec);
121        validator.validate_header(header)
122    }
123
124    fn transact<DB: Database>(
125        input: &ContractInput,
126        db: DB,
127        header: &Header,
128        difficulty: U256,
129        chain_spec: Arc<Self::ChainSpec>,
130    ) -> Result<ResultAndState<Self::HaltReason>, String> {
131        let EvmEnv { mut cfg_env, mut block_env, .. } =
132            EthEvmConfig::new(chain_spec).evm_env(header);
133
134        // Set the base fee to 0 to enable 0 gas price transactions.
135        block_env.basefee = 0;
136        block_env.difficulty = difficulty;
137        cfg_env.disable_nonce_check = true;
138        cfg_env.disable_balance_check = true;
139
140        let evm = Context::mainnet()
141            .with_db(db)
142            .with_cfg(cfg_env)
143            .with_block(block_env)
144            .modify_tx_chained(|tx_env| {
145                tx_env.gas_limit = header.gas_limit;
146            })
147            .build_mainnet_with_inspector(NoOpInspector {});
148
149        let mut evm = EthEvm::new(evm, false);
150
151        evm.transact(input).map_err(|err| err.to_string())
152    }
153
154    fn active_fork_name(chain_spec: &Self::ChainSpec, header: &Header) -> String {
155        let spec = reth_evm_ethereum::revm_spec(chain_spec, header);
156
157        spec.to_string()
158    }
159}
160
161#[cfg(feature = "optimism")]
162impl Primitives for reth_optimism_primitives::OpPrimitives {
163    type ChainSpec = reth_optimism_chainspec::OpChainSpec;
164    type HaltReason = op_revm::OpHaltReason;
165
166    fn build_spec(genesis: &Genesis) -> Result<Arc<Self::ChainSpec>, ClientError> {
167        Ok(Arc::new(reth_optimism_chainspec::OpChainSpec::try_from(genesis).unwrap()))
168    }
169
170    fn validate_header(
171        header: &SealedHeader,
172        chain_spec: Arc<Self::ChainSpec>,
173    ) -> Result<(), ConsensusError> {
174        let validator = reth_optimism_consensus::OpBeaconConsensus::new(chain_spec);
175        validator.validate_header(header)
176    }
177
178    fn transact<DB: Database>(
179        input: &ContractInput,
180        db: DB,
181        header: &Header,
182        difficulty: U256,
183        chain_spec: Arc<Self::ChainSpec>,
184    ) -> Result<ResultAndState<Self::HaltReason>, String> {
185        use op_revm::{DefaultOp, OpBuilder};
186
187        let EvmEnv { mut cfg_env, mut block_env, .. } =
188            reth_optimism_evm::OpEvmConfig::optimism(chain_spec).evm_env(header);
189
190        // Set the base fee to 0 to enable 0 gas price transactions.
191        block_env.basefee = 0;
192        block_env.difficulty = difficulty;
193        cfg_env.disable_nonce_check = true;
194        cfg_env.disable_balance_check = true;
195
196        let evm = op_revm::OpContext::op()
197            .with_db(db)
198            .with_cfg(cfg_env)
199            .with_block(block_env)
200            .modify_tx_chained(|tx_env| {
201                tx_env.base.gas_limit = header.gas_limit;
202            })
203            .build_op_with_inspector(NoOpInspector {});
204
205        let mut evm = alloy_op_evm::OpEvm::new(evm, false);
206
207        evm.transact(input).map_err(|err| err.to_string())
208    }
209
210    fn active_fork_name(chain_spec: &Self::ChainSpec, header: &Header) -> String {
211        let spec = reth_optimism_evm::revm_spec(chain_spec, header);
212        let spec: &'static str = spec.into();
213
214        spec.to_string()
215    }
216}