Skip to content

Commit 49ac1e3

Browse files
committed
Add prove-dryrun script
Lint code
1 parent 00cc287 commit 49ac1e3

File tree

3 files changed

+325
-0
lines changed

3 files changed

+325
-0
lines changed

fault-proof/src/contract.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ sol! {
6868
/// @notice Getter for the parent hash of the L1 block when the dispute game was created.
6969
function l1Head() public pure returns (Hash l1Head_);
7070

71+
/// @notice Returns the rollup config hash.
72+
function rollupConfigHash() public pure returns (Hash rollupConfigHash_);
73+
74+
/// @notice Returns the aggregation vkey.
75+
function aggregationVkey() public pure returns (Hash aggregationVkey_);
76+
77+
/// @notice Returns the range vkey commitment.
78+
function rangeVkeyCommitment() public pure returns (Hash rangeVkeyCommitment_);
79+
7180
/// @notice Getter for the status of the game.
7281
function status() public view returns (GameStatus status_);
7382

scripts/utils/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ path = "bin/parallel_cost_estimator.rs"
4747
name = "preflight"
4848
path = "bin/preflight.rs"
4949

50+
[[bin]]
51+
name = "prove-dryrun"
52+
path = "bin/prove_dryrun.rs"
53+
5054
[dependencies]
5155

5256
# workspace

scripts/utils/bin/prove_dryrun.rs

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
use std::{env, fs, path::PathBuf, str::FromStr, sync::Arc};
2+
3+
use alloy_eips::BlockNumberOrTag;
4+
use alloy_network::EthereumWallet;
5+
use alloy_node_bindings::Anvil;
6+
use alloy_primitives::{Address, B256, U256};
7+
use alloy_provider::{Provider, ProviderBuilder};
8+
use alloy_signer_local::PrivateKeySigner;
9+
use alloy_transport_http::reqwest::Url;
10+
use anyhow::{anyhow, Context, Result};
11+
use clap::Parser;
12+
use fault_proof::contract::{DisputeGameFactory, OPSuccinctFaultDisputeGame, ProposalStatus};
13+
use op_succinct_client_utils::{boot::BootInfoStruct, types::u32_to_u8};
14+
use op_succinct_elfs::AGGREGATION_ELF;
15+
use op_succinct_host_utils::{
16+
fetcher::OPSuccinctDataFetcher,
17+
get_agg_proof_stdin,
18+
host::OPSuccinctHost,
19+
network::{determine_network_mode, get_network_signer, parse_fulfillment_strategy},
20+
witness_generation::WitnessGenerator,
21+
};
22+
use op_succinct_proof_utils::{get_range_elf_embedded, initialize_host};
23+
use sp1_sdk::{
24+
network::FulfillmentStrategy, utils, HashableKey, Prover, ProverClient, SP1ProofMode,
25+
};
26+
use tracing::info;
27+
28+
#[derive(Parser, Debug)]
29+
#[command(author, version, about, long_about = None)]
30+
struct Args {
31+
/// The environment file path.
32+
#[arg(long, default_value = ".env.prove_dryrun")]
33+
env_file: PathBuf,
34+
35+
/// Index of the game to prove.
36+
#[arg(long, value_parser = clap::value_parser!(u64).range(1..))]
37+
index: u64,
38+
}
39+
40+
#[derive(Debug, Clone)]
41+
struct Config {
42+
/// The L1 RPC URL.
43+
pub l1_rpc: Url,
44+
45+
/// The address of the factory contract.
46+
pub factory_address: Address,
47+
48+
/// Proof fulfillment strategy for range proofs.
49+
pub range_proof_strategy: FulfillmentStrategy,
50+
51+
/// Proof fulfillment strategy for aggregation proofs.
52+
pub agg_proof_strategy: FulfillmentStrategy,
53+
54+
// Aggregation proof mode (plonk/groth16)
55+
pub agg_proof_mode: String,
56+
57+
/// Proposer private key
58+
pub private_key: String,
59+
60+
/// Whether to expect NETWORK_PRIVATE_KEY to be an AWS KMS key ARN instead of a
61+
/// plaintext private key.
62+
pub use_kms_requester: bool,
63+
}
64+
65+
impl Config {
66+
pub fn from_env() -> Result<Self> {
67+
Ok(Self {
68+
l1_rpc: env::var("L1_RPC")
69+
.context("L1_RPC must be set")?
70+
.parse()
71+
.expect("failed to parse L1_RPC"),
72+
factory_address: env::var("FACTORY_ADDRESS")
73+
.context("FACTORY_ADDRESS must be set")?
74+
.parse()
75+
.expect("failed to parse FACTORY_ADDRESS"),
76+
range_proof_strategy: parse_fulfillment_strategy(
77+
env::var("RANGE_PROOF_STRATEGY").unwrap_or("reserved".to_string()),
78+
),
79+
agg_proof_strategy: parse_fulfillment_strategy(
80+
env::var("AGG_PROOF_STRATEGY").unwrap_or("reserved".to_string()),
81+
),
82+
agg_proof_mode: env::var("AGG_PROOF_MODE")
83+
.ok()
84+
.filter(|s| !s.trim().is_empty())
85+
.unwrap_or_else(|| "plonk".to_string()),
86+
private_key: env::var("PRIVATE_KEY")
87+
.context("PRIVATE_KEY must be set")?
88+
.parse()
89+
.expect("failed to parse PRIVATE_KEY"),
90+
use_kms_requester: env::var("USE_KMS_REQUESTER")
91+
.unwrap_or("false".to_string())
92+
.parse()?,
93+
})
94+
}
95+
}
96+
97+
/// Preflight check for the OP Succinct Fault Dispute Game.
98+
#[tokio::main]
99+
async fn main() -> Result<()> {
100+
// 1. Set up the environment.
101+
utils::setup_logger();
102+
103+
let args = Args::parse();
104+
105+
dotenv::from_path(&args.env_file)
106+
.context(format!("Environment file not found: {}", args.env_file.display()))?;
107+
108+
let config = Config::from_env()?;
109+
110+
let wallet =
111+
PrivateKeySigner::from_str(&config.private_key).context("failed to parse private key")?;
112+
113+
let network_signer = get_network_signer(config.use_kms_requester).await?;
114+
let network_mode =
115+
determine_network_mode(config.range_proof_strategy, config.agg_proof_strategy).context(
116+
"failed to determine network mode from range and agg fulfillment strategies",
117+
)?;
118+
119+
let data_fetcher = OPSuccinctDataFetcher::new_with_rollup_config().await?;
120+
121+
let factory = DisputeGameFactory::new(config.factory_address, data_fetcher.l1_provider.clone());
122+
123+
let parent_game = OPSuccinctFaultDisputeGame::new(
124+
factory
125+
.gameAtIndex(U256::from(args.index - 1))
126+
.call()
127+
.await
128+
.with_context(|| {
129+
format!("failed to fetch the parent game at index {}", args.index - 1)
130+
})?
131+
.proxy,
132+
data_fetcher.l1_provider.clone(),
133+
);
134+
let game = OPSuccinctFaultDisputeGame::new(
135+
factory
136+
.gameAtIndex(U256::from(args.index))
137+
.call()
138+
.await
139+
.with_context(|| format!("failed to fetch the game at index {}", args.index))?
140+
.proxy,
141+
data_fetcher.l1_provider.clone(),
142+
);
143+
144+
info!("Proving for Game #{} (address: {})", args.index, game.address());
145+
146+
let l1_head_hash: [u8; 32] = game.l1Head().call().await?.0;
147+
let l2_start_block = parent_game.l2BlockNumber().call().await?.to::<u64>();
148+
let l2_end_block = game.l2BlockNumber().call().await?.to::<u64>();
149+
150+
let l1_head = data_fetcher
151+
.l1_provider
152+
.get_block_by_hash(l1_head_hash.into())
153+
.await?
154+
.expect("failed to fetch L1 head block")
155+
.header;
156+
157+
info!(
158+
l2_start_block,
159+
l2_end_block,
160+
l1_head_number = l1_head.number,
161+
l1_head_hash = %l1_head.hash,
162+
"Proving L2 block range against L1 head"
163+
);
164+
165+
// 2. Generate the range proof.
166+
let host = initialize_host(Arc::new(data_fetcher.clone()));
167+
let host_args =
168+
host.fetch(l2_start_block, l2_end_block, Some(l1_head_hash.into()), false).await?;
169+
170+
info!("Generating range proof witness data...");
171+
let witness_data = host.run(&host_args).await?;
172+
info!("Range proof witness data generated successfully");
173+
174+
info!("Getting range proof stdin...");
175+
let range_proof_stdin = host.witness_generator().get_sp1_stdin(witness_data)?;
176+
info!("Range proof stdin generated successfully");
177+
178+
// Initialize the network prover.
179+
let network_prover =
180+
ProverClient::builder().network_for(network_mode).signer(network_signer.clone()).build();
181+
info!("Initialized network prover successfully");
182+
183+
let (range_pk, range_vk) = network_prover.setup(get_range_elf_embedded());
184+
let mut range_proof = network_prover
185+
.prove(&range_pk, &range_proof_stdin)
186+
.compressed()
187+
.strategy(config.range_proof_strategy)
188+
.run()
189+
.unwrap();
190+
191+
// Save the proof to the proof directory corresponding to the chain ID.
192+
let range_proof_dir =
193+
format!("data/{}/proofs/range", data_fetcher.get_l2_chain_id().await.unwrap());
194+
if !std::path::Path::new(&range_proof_dir).exists() {
195+
fs::create_dir_all(&range_proof_dir).unwrap();
196+
}
197+
range_proof
198+
.save(format!("{range_proof_dir}/{l2_start_block}-{l2_end_block}.bin"))
199+
.expect("saving proof failed");
200+
info!("Range proof saved to {range_proof_dir}/{l2_start_block}-{l2_end_block}.bin");
201+
202+
// Validation
203+
let boot_info: BootInfoStruct = range_proof.public_values.read();
204+
205+
info!("BootInfo L1 head: {:?}", boot_info.l1Head);
206+
info!("Game L1 head: {:?}", l1_head.hash);
207+
assert_eq!(boot_info.l1Head, l1_head.hash, "L1 head hash mismatch");
208+
209+
let game_root_claim = game.rootClaim().call().await?;
210+
info!("Boot Info L2PostRoot: {:?}", boot_info.l2PostRoot);
211+
info!("Game Root Claim: {:?}", game_root_claim);
212+
assert_eq!(boot_info.l2PostRoot, game_root_claim, "Root claim mismatch");
213+
214+
let game_rollup_config_hash = game.rollupConfigHash().call().await?;
215+
info!("Boot Info Rollup Config Hash: {:?}", boot_info.rollupConfigHash);
216+
info!("Game Rollup Config Hash: {:?}", game_rollup_config_hash);
217+
assert_eq!(boot_info.rollupConfigHash, game_rollup_config_hash, "Rollup config hash mismatch");
218+
219+
let range_vk_hash = B256::from(u32_to_u8(range_vk.vk.hash_u32()));
220+
let game_range_v_key_hash = game.rangeVkeyCommitment().call().await?;
221+
info!("Range Verification Key Hash: {:?}", range_vk_hash);
222+
info!("Game Range Verification Key Hash: {:?}", game_range_v_key_hash);
223+
assert_eq!(range_vk_hash, game_range_v_key_hash, "Range verification key hash mismatch");
224+
225+
// 3. Generate the aggregation proof.
226+
let network_prover =
227+
ProverClient::builder().network_for(network_mode).signer(network_signer).build();
228+
info!("Initialized network prover successfully");
229+
230+
let agg_proof_stdin = get_agg_proof_stdin(
231+
vec![range_proof.proof],
232+
vec![boot_info.clone()],
233+
vec![l1_head.clone().into()],
234+
&range_vk,
235+
boot_info.l1Head,
236+
wallet.address(),
237+
)
238+
.context("failed to get agg proof stdin")?;
239+
240+
let agg_proof_mode = match config.agg_proof_mode.to_lowercase().as_str() {
241+
"groth16" => SP1ProofMode::Groth16,
242+
"plonk" => SP1ProofMode::Plonk,
243+
other => {
244+
return Err(anyhow!(
245+
"Invalid AGG_PROOF_MODE '{}'. Expected one of: plonk, groth16",
246+
other
247+
))
248+
}
249+
};
250+
info!("Aggregation proof mode: {:?}", agg_proof_mode);
251+
252+
let (agg_pk, agg_vk) = network_prover.setup(AGGREGATION_ELF);
253+
254+
let agg_vk_hash = agg_vk.bytes32();
255+
let game_range_aggregation_v_key = game.aggregationVkey().call().await?.to_string();
256+
info!("Aggregation Verification Key: {:?}", range_vk_hash);
257+
info!("Game Aggregation Verification Key: {}", game_range_aggregation_v_key);
258+
assert_eq!(
259+
agg_vk_hash, game_range_aggregation_v_key,
260+
"Aggregation verification key hash mismatch"
261+
);
262+
263+
let agg_proof = network_prover
264+
.prove(&agg_pk, &agg_proof_stdin)
265+
.mode(agg_proof_mode)
266+
.strategy(config.agg_proof_strategy)
267+
.run()
268+
.unwrap();
269+
270+
let agg_proof_dir =
271+
format!("data/{}/proofs/agg", data_fetcher.get_l2_chain_id().await.unwrap());
272+
if !std::path::Path::new(&agg_proof_dir).exists() {
273+
fs::create_dir_all(&agg_proof_dir).unwrap();
274+
}
275+
276+
agg_proof.save(format!("{agg_proof_dir}/agg.bin")).expect("saving proof failed");
277+
info!("Agg proof saved to {agg_proof_dir}/agg.bin");
278+
279+
// 4. Spin up anvil.
280+
let fork_number = l1_head.number + 1;
281+
282+
let anvil =
283+
Anvil::new().fork(config.l1_rpc).fork_block_number(fork_number).args(["--no-mining"]);
284+
let anvil_instance = anvil.spawn();
285+
let endpoint = anvil_instance.endpoint();
286+
info!("Anvil chain started forked from L1 block number: {} at: {}", fork_number, endpoint);
287+
288+
// 5. Run the preflight check.
289+
let provider_with_signer = ProviderBuilder::new()
290+
.wallet(EthereumWallet::from(wallet))
291+
.connect_http(Url::parse(&endpoint)?);
292+
293+
let game = OPSuccinctFaultDisputeGame::new(*game.address(), provider_with_signer.clone());
294+
295+
let tx = game.prove(agg_proof.bytes().into()).send().await?;
296+
297+
let client = provider_with_signer.client();
298+
let _: String = client.request("evm_mine", Vec::<serde_json::Value>::new()).await?;
299+
300+
let block = provider_with_signer.get_block_by_number(BlockNumberOrTag::Latest).await?;
301+
info!("Mined block: {}", block.unwrap().header.number);
302+
303+
let receipt = tx.get_receipt().await?;
304+
info!("Transaction receipt: {:?}", receipt);
305+
306+
let claim_data = game.claimData().call().await?;
307+
assert_eq!(claim_data.status, ProposalStatus::UnchallengedAndValidProofProvided);
308+
309+
info!("Prove dry-run completed successfully");
310+
311+
Ok(())
312+
}

0 commit comments

Comments
 (0)