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