Skip to content

Commit 0a6b6cc

Browse files
authored
feat: handle custom chains (#34)
* feat: handle custom chains * fix: add `new_with_genesis` fn * fix: CI
1 parent 4752ceb commit 0a6b6cc

File tree

10 files changed

+73
-40
lines changed

10 files changed

+73
-40
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/client-executor/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ rsp-witness-db.workspace = true
1818
rsp-primitives.workspace = true
1919
rsp-client-executor.workspace = true
2020
rsp-mpt.workspace = true
21+
22+
# reth
23+
reth-chainspec.workspace = true
2124
reth-primitives.workspace = true
2225
reth-evm.workspace = true
2326
reth-evm-ethereum.workspace = true

crates/client-executor/src/io.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use revm::state::Bytecode;
55
use revm_primitives::{Address, HashMap, B256, U256};
66
use rsp_client_executor::io::WitnessInput;
77
use rsp_mpt::EthereumState;
8+
use rsp_primitives::genesis::Genesis;
89
use serde::{Deserialize, Serialize};
910
use serde_with::serde_as;
1011

@@ -16,6 +17,8 @@ use serde_with::serde_as;
1617
#[serde_as]
1718
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1819
pub struct EVMStateSketch {
20+
/// The genesis block specification.
21+
pub genesis: Genesis,
1922
/// The current block header.
2023
#[serde_as(as = "alloy_consensus::serde_bincode_compat::Header")]
2124
pub header: Header,

crates/client-executor/src/lib.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use alloy_evm::{Database, Evm, IntoTxEnv};
55
use alloy_sol_types::{sol, SolCall};
66
use eyre::OptionExt;
77
use io::EVMStateSketch;
8+
use reth_chainspec::ChainSpec;
89
use reth_evm::{ConfigureEvm, EvmEnv};
910
use reth_evm_ethereum::{EthEvm, EthEvmConfig};
1011
use reth_primitives::Header;
@@ -13,6 +14,7 @@ use revm::{
1314
};
1415
use revm_primitives::{Address, Bytes, TxKind, B256, U256};
1516
use rsp_client_executor::io::{TrieDB, WitnessInput};
17+
use rsp_primitives::genesis::Genesis;
1618

1719
/// Input to a contract call.
1820
///
@@ -84,6 +86,7 @@ impl IntoTxEnv<TxEnv> for &ContractInput {
8486
ContractCalldata::Create(_) => TxKind::Create,
8587
ContractCalldata::Call(_) => TxKind::Call(self.contract_address),
8688
},
89+
chain_id: None,
8790
..Default::default()
8891
}
8992
}
@@ -121,6 +124,8 @@ impl ContractPublicValues {
121124
/// An executor that executes smart contract calls inside a zkVM.
122125
#[derive(Debug)]
123126
pub struct ClientExecutor<'a> {
127+
/// The genesis block specification.
128+
pub genesis: &'a Genesis,
124129
/// The database that the executor uses to access state.
125130
pub witness_db: TrieDB<'a>,
126131
/// The block header.
@@ -131,34 +136,42 @@ impl<'a> ClientExecutor<'a> {
131136
/// Instantiates a new [`ClientExecutor`]
132137
pub fn new(state_sketch: &'a EVMStateSketch) -> eyre::Result<Self> {
133138
// let header = state_sketch.header.clone();
134-
Ok(Self { witness_db: state_sketch.witness_db().unwrap(), header: &state_sketch.header })
139+
Ok(Self {
140+
genesis: &state_sketch.genesis,
141+
witness_db: state_sketch.witness_db().unwrap(),
142+
header: &state_sketch.header,
143+
})
135144
}
136145

137146
/// Executes the smart contract call with the given [`ContractInput`] in SP1.
138147
///
139148
/// Storage accesses are already validated against the `witness_db`'s state root.
140149
pub fn execute(&self, call: ContractInput) -> eyre::Result<ContractPublicValues> {
141150
let cache_db = CacheDB::new(&self.witness_db);
142-
let mut evm = new_evm(cache_db, self.header, U256::ZERO);
151+
let mut evm = new_evm(cache_db, self.header, U256::ZERO, self.genesis);
143152
let tx_output = evm.transact(&call)?;
144153
let tx_output_bytes = tx_output.result.output().ok_or_eyre("Error decoding result")?;
145154
Ok(ContractPublicValues::new(call, tx_output_bytes.clone(), self.header.hash_slow()))
146155
}
147156
}
148157

149-
/// TODO Add support for other chains besides Ethereum Mainnet.
150158
/// Instantiates a new EVM, which is ready to run `call`.
151-
pub fn new_evm<DB>(db: DB, header: &Header, total_difficulty: U256) -> EthEvm<DB, NoOpInspector>
159+
pub fn new_evm<DB>(
160+
db: DB,
161+
header: &Header,
162+
difficulty: U256,
163+
genesis: &Genesis,
164+
) -> EthEvm<DB, NoOpInspector>
152165
where
153166
DB: Database,
154167
{
155-
let chain_spec = Arc::new(rsp_primitives::chain_spec::mainnet().unwrap());
168+
let chain_spec = Arc::new(ChainSpec::try_from(genesis).unwrap());
156169

157170
let EvmEnv { cfg_env, mut block_env, .. } = EthEvmConfig::new(chain_spec).evm_env(header);
158171

159172
// Set the base fee to 0 to enable 0 gas price transactions.
160173
block_env.basefee = 0;
161-
block_env.difficulty = total_difficulty;
174+
block_env.difficulty = difficulty;
162175

163176
let evm = Context::mainnet()
164177
.with_db(db)

crates/host-executor/src/lib.rs

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use std::collections::BTreeSet;
66
use alloy_consensus::Header;
77
use alloy_evm::Evm;
88
use alloy_provider::{network::AnyNetwork, Provider};
9-
use alloy_rpc_types::{BlockId, BlockNumberOrTag};
10-
use eyre::eyre;
9+
use alloy_rpc_types::BlockId;
10+
use eyre::{eyre, Ok};
1111
use revm::database::CacheDB;
1212
use revm_primitives::{Bytes, B256, U256};
1313
use rsp_mpt::EthereumState;
@@ -16,12 +16,16 @@ use rsp_rpc_db::RpcDb;
1616

1717
use sp1_cc_client_executor::{io::EVMStateSketch, new_evm, ContractInput};
1818

19+
pub use rsp_primitives::genesis::Genesis;
20+
1921
/// An executor that fetches data from a [`Provider`].
2022
///
2123
/// This executor keeps track of the state being accessed, and eventually compresses it into an
2224
/// [`EVMStateSketch`].
2325
#[derive(Debug, Clone)]
2426
pub struct HostExecutor<P: Provider<AnyNetwork> + Clone> {
27+
/// The genesis block specification.
28+
pub genesis: Genesis,
2529
/// The header of the block to execute our view functions on.
2630
pub header: Header,
2731
/// The [`RpcDb`] used to back the EVM.
@@ -31,28 +35,20 @@ pub struct HostExecutor<P: Provider<AnyNetwork> + Clone> {
3135
}
3236

3337
impl<P: Provider<AnyNetwork> + Clone> HostExecutor<P> {
34-
/// Create a new [`HostExecutor`] with a specific [`Provider`] and [`BlockNumberOrTag`].
35-
pub async fn new(provider: P, block_number: BlockNumberOrTag) -> eyre::Result<Self> {
36-
let block = provider
37-
.get_block_by_number(block_number)
38-
.full()
39-
.await?
40-
.ok_or(eyre!("couldn't fetch block: {}", block_number))?;
41-
42-
let rpc_db = RpcDb::new(provider.clone(), block.header.number);
43-
let header = block
44-
.inner
45-
.header
46-
.inner
47-
.clone()
48-
.try_into_header()
49-
.map_err(|_| eyre!("fail to convert header"))?;
50-
51-
Ok(Self { header, rpc_db, provider })
38+
/// Creates a new [`HostExecutor`] with a specific [`Provider`] and `block_identifier`
39+
/// on Ethereum Mainnet. For a custom chain, use [`HostExecutor::new_with_genesis`].
40+
pub async fn new<B: Into<BlockId>>(provider: P, block_identifier: B) -> eyre::Result<Self> {
41+
Self::new_with_genesis(provider, block_identifier, Genesis::Mainnet).await
5242
}
5343

54-
/// Create a new [`HostExecutor`] with a specific [`Provider`] and [`BlockId`].
55-
pub async fn new_with_blockid(provider: P, block_identifier: BlockId) -> eyre::Result<Self> {
44+
/// Creates a new [`HostExecutor`] with a specific [`Provider`] and `block_identifier`
45+
/// on a custom chain using the provided [`Genesis`].
46+
pub async fn new_with_genesis<B: Into<BlockId>>(
47+
provider: P,
48+
block_identifier: B,
49+
genesis: Genesis,
50+
) -> eyre::Result<Self> {
51+
let block_identifier = block_identifier.into();
5652
let block = provider
5753
.get_block(block_identifier)
5854
.full()
@@ -67,15 +63,26 @@ impl<P: Provider<AnyNetwork> + Clone> HostExecutor<P> {
6763
.clone()
6864
.try_into_header()
6965
.map_err(|_| eyre!("fail to convert header"))?;
70-
Ok(Self { header, rpc_db, provider })
66+
67+
Ok(Self { genesis, header, rpc_db, provider })
7168
}
7269

7370
/// Executes the smart contract call with the given [`ContractInput`].
74-
pub async fn execute(&mut self, call: ContractInput) -> eyre::Result<Bytes> {
71+
pub async fn execute(&self, call: ContractInput) -> eyre::Result<Bytes> {
7572
let cache_db = CacheDB::new(&self.rpc_db);
76-
let mut evm = new_evm(cache_db, &self.header, U256::ZERO);
73+
let mut evm = new_evm(cache_db, &self.header, U256::ZERO, &self.genesis);
74+
7775
let output = evm.transact(&call)?;
78-
let output_bytes = output.result.output().ok_or(eyre!("Error getting result"))?;
76+
77+
let output_bytes = match output.result {
78+
revm::context::result::ExecutionResult::Success { output, .. } => {
79+
Ok(output.data().clone())
80+
}
81+
revm::context::result::ExecutionResult::Revert { output, .. } => Ok(output),
82+
revm::context::result::ExecutionResult::Halt { reason, .. } => {
83+
Err(eyre!("Execution halted: {reason:?}"))
84+
}
85+
}?;
7986

8087
Ok(output_bytes.clone())
8188
}
@@ -124,6 +131,7 @@ impl<P: Provider<AnyNetwork> + Clone> HostExecutor<P> {
124131
}
125132

126133
Ok(EVMStateSketch {
134+
genesis: self.genesis.clone(),
127135
header: self.header.clone(),
128136
ancestor_headers,
129137
state,

crates/host-executor/src/test.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use alloy_rpc_types::BlockNumberOrTag;
44
use alloy_sol_macro::sol;
55
use alloy_sol_types::SolCall;
66
use revm_primitives::{hex, Bytes};
7+
use rsp_primitives::genesis::Genesis;
78
use sp1_cc_client_executor::{ClientExecutor, ContractInput, ContractPublicValues};
89
use url::Url;
910
use ERC20Basic::nameCall;
@@ -131,7 +132,8 @@ async fn test_contract_creation() -> eyre::Result<()> {
131132
let rpc_url = std::env::var("ETH_SEPOLIA_RPC_URL")
132133
.unwrap_or_else(|_| panic!("Missing ETH_SEPOLIA_RPC_URL in env"));
133134
let provider = RootProvider::new_http(Url::parse(&rpc_url)?);
134-
let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?;
135+
let host_executor =
136+
HostExecutor::new_with_genesis(provider.clone(), block_number, Genesis::Sepolia).await?;
135137

136138
// Keep track of the block hash. Later, validate the client's execution against this.
137139
let bytes = hex::decode(bytecode).expect("Decoding failed");
@@ -158,7 +160,8 @@ async fn test_e2e(contract_input: ContractInput) -> eyre::Result<ContractPublicV
158160
// Use `RPC_URL` to get all of the necessary state for the smart contract call.
159161
let rpc_url = std::env::var("ETH_RPC_URL").unwrap_or_else(|_| panic!("Missing RPC_URL"));
160162
let provider = RootProvider::new_http(Url::parse(&rpc_url)?);
161-
let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?;
163+
let host_executor =
164+
HostExecutor::new_with_genesis(provider.clone(), block_number, Genesis::Sepolia).await?;
162165

163166
let _contract_output = host_executor.execute(contract_input.clone()).await?;
164167

examples/example-deploy/host/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use alloy_provider::RootProvider;
99
use alloy_rpc_types::BlockNumberOrTag;
1010
use alloy_sol_types::SolValue;
1111
use sp1_cc_client_executor::ContractInput;
12-
use sp1_cc_host_executor::HostExecutor;
12+
use sp1_cc_host_executor::{Genesis, HostExecutor};
1313
use url::Url;
1414

1515
/// The following bytecode corresponds to the following solidity contract:
@@ -42,7 +42,8 @@ async fn main() -> eyre::Result<()> {
4242
let rpc_url = std::env::var("ETH_SEPOLIA_RPC_URL")
4343
.unwrap_or_else(|_| panic!("Missing ETH_SEPOLIA_RPC_URL in env"));
4444
let provider = RootProvider::new_http(Url::parse(&rpc_url)?);
45-
let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?;
45+
let host_executor =
46+
HostExecutor::new_with_genesis(provider.clone(), block_number, Genesis::Sepolia).await?;
4647

4748
// Keep track of the block hash. Later, validate the client's execution against this.
4849
let bytes = hex::decode(BYTECODE).expect("Decoding failed");

examples/multiplexer/host/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async fn main() -> eyre::Result<()> {
5555
let rpc_url =
5656
std::env::var("ETH_RPC_URL").unwrap_or_else(|_| panic!("Missing ETH_RPC_URL in env"));
5757
let provider = RootProvider::new_http(Url::parse(&rpc_url)?);
58-
let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?;
58+
let host_executor = HostExecutor::new(provider.clone(), block_number).await?;
5959

6060
// Keep track of the block hash. Later, the client's execution will be validated against this.
6161
let block_hash = host_executor.header.hash_slow();

examples/uniswap/host/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async fn main() -> eyre::Result<()> {
8282
let rpc_url =
8383
std::env::var("ETH_RPC_URL").unwrap_or_else(|_| panic!("Missing ETH_RPC_URL in env"));
8484
let provider = RootProvider::new_http(Url::parse(&rpc_url)?);
85-
let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?;
85+
let host_executor = HostExecutor::new(provider.clone(), block_number).await?;
8686

8787
// Keep track of the block hash. Later, validate the client's execution against this.
8888
let block_hash = host_executor.header.hash_slow();

examples/verify-quorum/host/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rand_chacha::ChaCha20Rng;
77
use rand_core::{RngCore, SeedableRng};
88
use secp256k1::{generate_keypair, Message, PublicKey, SECP256K1};
99
use sp1_cc_client_executor::{ContractInput, ContractPublicValues};
10-
use sp1_cc_host_executor::HostExecutor;
10+
use sp1_cc_host_executor::{Genesis, HostExecutor};
1111
use sp1_sdk::{include_elf, utils, ProverClient, SP1Stdin};
1212
use url::Url;
1313
use SimpleStaking::verifySignedCall;
@@ -58,7 +58,8 @@ async fn main() -> eyre::Result<()> {
5858
let rpc_url = std::env::var("ETH_SEPOLIA_RPC_URL")
5959
.unwrap_or_else(|_| panic!("Missing ETH_SEPOLIA_RPC_URL in env"));
6060
let provider = RootProvider::new_http(Url::parse(&rpc_url)?);
61-
let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?;
61+
let host_executor =
62+
HostExecutor::new_with_genesis(provider.clone(), block_number, Genesis::Sepolia).await?;
6263

6364
// Keep track of the block hash. Later, validate the client's execution against this.
6465
let block_hash = host_executor.header.hash_slow();

0 commit comments

Comments
 (0)