From 90dd9abd6e7da8b82afd0548f284c8ed085e8675 Mon Sep 17 00:00:00 2001 From: Alex Metelli Date: Wed, 8 Jan 2025 10:44:50 +0800 Subject: [PATCH] Retries for proofs and starknet (#14) * feat: enhance MMR build options and refactor RPC handling Add new MMR build options: - Add --start-block (-s) to start building from specific block - Add --from-latest (-l) to start from latest MMR block - Add validation to prevent conflicting options - Update help text for all CLI options Refactor RPC URL handling: - Move RPC URL handling from StarknetAccount to AccumulatorBuilder - Add starknet_rpc_url field to AccumulatorBuilder struct - Update AccumulatorBuilder::new signature to accept RPC URL - Remove RPC URL handling from StarknetAccount New AccumulatorBuilder methods: - Add build_from_block for starting from specific block - Add build_from_latest for starting from latest MMR block - Add build_from_block_with_batches for batch processing - Add build_from_latest_with_batches for batch processing This change improves flexibility in MMR building by: 1. Allowing users to specify custom start points 2. Supporting continuation from latest MMR state 3. Making the RPC URL dependency explicit 4. Simplifying provider creation for latest block queries * remove unused update_mmr_state from starknet account * implemented retries for starkent proof verification tx * fixes for mmr build restart + added min block to starknet store * scarb fmt * updated cairo in ci --- .github/workflows/cairo.yml | 2 +- Cargo.lock | 1 + README.md | 55 ++++++ config/.env.docker.example | 31 ++++ config/.env.example | 27 --- config/anvil.messaging.docker.json | 6 +- contracts/starknet/store/src/lib.cairo | 35 +++- .../verifier/src/fossil_verifier.cairo | 8 +- contracts/starknet/verifier/src/lib.cairo | 8 +- crates/publisher/bin/build_mmr.rs | 36 +++- crates/publisher/src/api/operations.rs | 1 + crates/publisher/src/core/accumulator.rs | 172 +++++++++++++++++- crates/publisher/src/core/batch_processor.rs | 5 +- .../publisher/src/core/mmr_state_manager.rs | 11 +- crates/starknet-handler/Cargo.toml | 1 + crates/starknet-handler/src/account.rs | 96 +++++----- crates/starknet-handler/src/provider.rs | 27 +++ 17 files changed, 406 insertions(+), 116 deletions(-) create mode 100755 config/.env.docker.example delete mode 100644 config/.env.example diff --git a/.github/workflows/cairo.yml b/.github/workflows/cairo.yml index f4e6212..6fe63e7 100644 --- a/.github/workflows/cairo.yml +++ b/.github/workflows/cairo.yml @@ -10,7 +10,7 @@ jobs: submodules: true - uses: software-mansion/setup-scarb@v1 with: - scarb-version: "2.8.5" + scarb-version: "2.9.1" - run: cd contracts/starknet && scarb fmt --check - run: cd contracts/starknet && scarb build # - run: snforge test diff --git a/Cargo.lock b/Cargo.lock index 0343cbe..68ed97c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5352,6 +5352,7 @@ dependencies = [ "starknet", "starknet-crypto", "thiserror 2.0.9", + "tokio", "tracing", "url", ] diff --git a/README.md b/README.md index df57b7b..1d5f5f1 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,44 @@ Required toolchain components: ./scripts/deploy.sh ``` +#### Terminal 4: MMR Builder Options + +The MMR builder supports several options for controlling how the MMR is built: + +```bash +# Build MMR with default settings (from latest finalized block) +cargo run --bin build_mmr --release + +# Build from a specific start block +cargo run --bin build_mmr --release -- --start-block + +# Build from the latest onchain MMR block +cargo run --bin build_mmr --release -- --from-latest + +# Control batch size +cargo run --bin build_mmr --release -- --batch-size + +# Process specific number of batches +cargo run --bin build_mmr --release -- --num-batches + +# Skip proof verification +cargo run --bin build_mmr --release -- --skip-proof + +# Combine options (examples) +cargo run --bin build_mmr --release -- --from-latest --num-batches 10 +cargo run --bin build_mmr --release -- --start-block 1000 --batch-size 512 +``` + +Available options: +- `--start-block, -s`: Start building from this block number +- `--from-latest, -l`: Start building from the latest onchain MMR block +- `--batch-size`: Number of blocks per batch (default: 1024) +- `--num-batches, -n`: Number of batches to process +- `--skip-proof, -p`: Skip proof verification +- `--env-file, -e`: Path to environment file (default: .env) + +Note: `--from-latest` and `--start-block` cannot be used together. + #### Terminal 4: Light Client Process Execute client binary: @@ -249,6 +287,23 @@ Execute client binary: cargo run --bin client --release ``` +The client supports the following options: + +```bash +# Run with default settings (5 second polling interval) +cargo run --bin client --release + +# Run with custom polling interval (in seconds) +cargo run --bin client --release -- --polling-interval 10 + +# Use a specific environment file +cargo run --bin client --release -- --env-file .env.local +``` + +Available options: +- `--polling-interval`: Time between polls in seconds (default: 5) +- `--env-file, -e`: Path to environment file (default: .env) + #### Terminal 5: Block Hash Relayer Process Execute relayer process: diff --git a/config/.env.docker.example b/config/.env.docker.example new file mode 100755 index 0000000..fda8328 --- /dev/null +++ b/config/.env.docker.example @@ -0,0 +1,31 @@ +ETH_RPC_URL=http://anvil:8545 +FORK_URL=http://209.127.228.66:8545 + +BONSAI_API_KEY=XXX +BONSAI_API_URL=https://api.bonsai.xyz/ + +DATABASE_URL=XXX + +ETH_ACCOUNT_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +ACCOUNT_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +SN_MESSAGING=0x0c77a52c35601106993B684E6b20D68FF0a89493 +L1_MESSAGE_SENDER=0x77F83238fa5FFD7E031C2D1195AC5d0a8D1B3df5 + +export FOUNDRY_EVM_VERSION=cancun + +ANVIL_CONFIG=config/anvil.messaging.json +STARKNET_RPC_URL=http://katana:5050 +STARKNET_PRIVATE_KEY=0xc5b2fcab997346f3ea1c00b002ecf6f382c5f9c9659a3894eb783c5320f912 +STARKNET_ACCOUNT_ADDRESS=0x127fd5f1fe78a71f8bcd1fec63e3fe2f0486b6ecd5c86a0466c3a21fa5cfcec + +L2_MSG_PROXY=0x01c0692f888adfb3dea3d4b4446afbfcd3a2456a64b113084af3549e8d6915e8 +FOSSIL_STORE=0x02d4eac88f8e9bbb0fd4ff2c35d385710bf3f8a6e4061266ac4de628dac11dd5 + +STARKNET_VERIFIER=0x03848e3432559e5804d48e1a60faa59c9ab00f515bcf122e3865193277eb118a +FOSSIL_VERIFIER=0x029d9eba080e8e7a6eed79f432b8d0cbe1e7ffe4187eda3cf2819cde8d700ff2 + +STARKNET_ACCOUNT=katana-0 + +DEPLOYMENT_VERSION=local +CHAIN_ID=11155111 \ No newline at end of file diff --git a/config/.env.example b/config/.env.example deleted file mode 100644 index df9caf9..0000000 --- a/config/.env.example +++ /dev/null @@ -1,27 +0,0 @@ - -BONSAI_API_KEY=xxx -BONSAI_API_URL=xxx - -DATABASE_URL=postgresql://postgres:PASSWORD@HOST:5432/DATABASE?sslmode=require - -ETH_RPC_URL=xxx - -ANVIL_URL=http://127.0.0.1:8545 -ACCOUNT_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -ETH_ACCOUNT_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -SN_MESSAGING=0xD185B4846E5fd5419fD4D077dc636084BEfC51C0 -L1_MESSAGE_SENDER=0xF94AB55a20B32AC37c3A105f12dB535986697945 - -export FOUNDRY_EVM_VERSION=cancun -ETH_WHALE=0x40B38765696e3d5d8d9d834D8AaD4bB6e418E489 - -STARKNET_RPC_URL=http://0.0.0.0:5050 -STARKNET_PRIVATE_KEY=0x2bbf4f9fd0bbb2e60b0316c1fe0b76cf7a4d0198bd493ced9b8df2a3a24d68a -STARKNET_ACCOUNT_ADDRESS=0xb3ff441a68610b30fd5e2abbf3a1548eb6ba6f3559f2862bf2dc757e5828ca - -L2_MSG_PROXY=0x02281b9682c26456419932bec6b0f90b16b1e903d94ad46cad55a1553c252d8d -FOSSIL_STORE=0x00bdb5b4bb53a2267e01dbe988cd683bd1b98f0aaa7a6a311e9d6c148a6aa739 -STARKNET_VERIFIER=0x0446639ae3cc1938476eb38990c28a4e02e702646733ff6e200bf80cf5cf8244 - -export STARKNET_ACCOUNT=katana-0 -export STARKNET_RPC=http://0.0.0.0:5050 diff --git a/config/anvil.messaging.docker.json b/config/anvil.messaging.docker.json index 435de0e..906604d 100644 --- a/config/anvil.messaging.docker.json +++ b/config/anvil.messaging.docker.json @@ -1,9 +1,9 @@ { "chain": "ethereum", - "rpc_url": "http://localhost:8545", - "contract_address": "0xE37675Bb1cc1c90dB0Deed8033CAb650770e79cE", + "rpc_url": "http://anvil:8545", + "contract_address": "0xe9FfA5399C14206e16D801dA5BD94cbF67bE82a7", "sender_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "interval": 2, - "from_block": 7422233 + "from_block": 7442506 } diff --git a/contracts/starknet/store/src/lib.cairo b/contracts/starknet/store/src/lib.cairo index 65b29a4..745d44b 100644 --- a/contracts/starknet/store/src/lib.cairo +++ b/contracts/starknet/store/src/lib.cairo @@ -1,11 +1,16 @@ #[starknet::interface] pub trait IFossilStore { - fn initialize(ref self: TContractState, verifier_address: starknet::ContractAddress, min_update_interval: u64); + fn initialize( + ref self: TContractState, + verifier_address: starknet::ContractAddress, + min_update_interval: u64, + ); fn store_latest_blockhash_from_l1(ref self: TContractState, block_number: u64, blockhash: u256); fn update_mmr_state(ref self: TContractState, journal: verifier::Journal); fn get_latest_blockhash_from_l1(self: @TContractState) -> (u64, u256); fn get_mmr_state(self: @TContractState, batch_index: u64) -> Store::MMRSnapshot; fn get_latest_mmr_block(self: @TContractState) -> u64; + fn get_min_mmr_block(self: @TContractState) -> u64; } #[starknet::contract] @@ -38,6 +43,7 @@ mod Store { latest_blockhash_from_l1: (u64, u256), latest_mmr_block: u64, mmr_batches: Map, + min_mmr_block: u64, min_update_interval: u64, } @@ -65,7 +71,11 @@ mod Store { #[abi(embed_v0)] impl FossilStoreImpl of super::IFossilStore { - fn initialize(ref self: ContractState, verifier_address: starknet::ContractAddress, min_update_interval: u64) { + fn initialize( + ref self: ContractState, + verifier_address: starknet::ContractAddress, + min_update_interval: u64, + ) { assert!(!self.initialized.read(), "Contract already initialized"); self.initialized.write(true); self.verifier_address.write(verifier_address); @@ -98,7 +108,7 @@ mod Store { actual_update_interval >= min_update_interval, "Update interval: {} must be greater than or equal to the minimum update interval: {}", actual_update_interval, - min_update_interval + min_update_interval, ); self.latest_mmr_block.write(journal.latest_mmr_block); } @@ -106,6 +116,17 @@ mod Store { let mut curr_state = self.mmr_batches.entry(journal.batch_index); curr_state.latest_mmr_block.write(journal.latest_mmr_block); + + let min_mmr_block = self.min_mmr_block.read(); + let lowest_batch_block = journal.latest_mmr_block - journal.leaves_count + 1; + if min_mmr_block != 0 { + if lowest_batch_block < min_mmr_block { + self.min_mmr_block.write(lowest_batch_block); + } + } else { + self.min_mmr_block.write(lowest_batch_block); + } + curr_state.latest_mmr_block_hash.write(journal.latest_mmr_block_hash); curr_state.leaves_count.write(journal.leaves_count); curr_state.root_hash.write(journal.root_hash); @@ -117,8 +138,8 @@ mod Store { latest_mmr_block: journal.latest_mmr_block, latest_mmr_block_hash: journal.latest_mmr_block_hash, leaves_count: journal.leaves_count, - root_hash: journal.root_hash - } + root_hash: journal.root_hash, + }, ); } @@ -136,5 +157,9 @@ mod Store { fn get_latest_mmr_block(self: @ContractState) -> u64 { self.latest_mmr_block.read() } + + fn get_min_mmr_block(self: @ContractState) -> u64 { + self.min_mmr_block.read() + } } } diff --git a/contracts/starknet/verifier/src/fossil_verifier.cairo b/contracts/starknet/verifier/src/fossil_verifier.cairo index 496edb6..63a4e8a 100644 --- a/contracts/starknet/verifier/src/fossil_verifier.cairo +++ b/contracts/starknet/verifier/src/fossil_verifier.cairo @@ -47,7 +47,11 @@ mod FossilVerifier { #[external(v0)] fn verify_mmr_proof(ref self: ContractState, proof: Span) -> bool { - let journal = self.bn254_verifier.read().verify_groth16_proof_bn254(proof).expect('Failed to verify proof'); + let journal = self + .bn254_verifier + .read() + .verify_groth16_proof_bn254(proof) + .expect('Failed to verify proof'); let journal = decode_journal(journal); @@ -59,7 +63,7 @@ mod FossilVerifier { batch_index: journal.batch_index, latest_mmr_block: journal.latest_mmr_block, new_leaves_count: journal.leaves_count, - new_mmr_root: journal.root_hash + new_mmr_root: journal.root_hash, }, ); diff --git a/contracts/starknet/verifier/src/lib.cairo b/contracts/starknet/verifier/src/lib.cairo index cb90935..8c5f78f 100644 --- a/contracts/starknet/verifier/src/lib.cairo +++ b/contracts/starknet/verifier/src/lib.cairo @@ -96,7 +96,7 @@ pub(crate) fn decode_journal(journal_bytes: Span) -> Journal { m += 1; }; - Journal { batch_index, latest_mmr_block, latest_mmr_block_hash, root_hash, leaves_count, } + Journal { batch_index, latest_mmr_block, latest_mmr_block_hash, root_hash, leaves_count } } trait BitShift { @@ -163,7 +163,7 @@ mod tests { assert_eq!(journal.latest_mmr_block, 7253851); assert_eq!( journal.latest_mmr_block_hash, - 0x858768dd79b8c6190fb224ff398345ffe4fcb9c4899c55e0fc0994b7d35177af + 0x858768dd79b8c6190fb224ff398345ffe4fcb9c4899c55e0fc0994b7d35177af, ); assert_eq!( journal.root_hash, 0x72aa9525dc9b7953631c0699d041fd4f23aa9f98c4a73aab27fbf2f0b9b451f8, @@ -340,8 +340,8 @@ mod tests { 0, 0, 0, - 0 + 0, ] .span() } -} \ No newline at end of file +} diff --git a/crates/publisher/bin/build_mmr.rs b/crates/publisher/bin/build_mmr.rs index 4897673..4fdc9d1 100644 --- a/crates/publisher/bin/build_mmr.rs +++ b/crates/publisher/bin/build_mmr.rs @@ -16,12 +16,20 @@ struct Args { num_batches: Option, /// Skip proof verification - #[arg(short, long, default_value_t = false)] + #[arg(short = 'p', long, default_value_t = false)] skip_proof: bool, /// Path to environment file (optional) #[arg(short = 'e', long, default_value = ".env")] env_file: String, + + /// Start building from this block number. If not specified, starts from the latest finalized block. + #[arg(short = 's', long)] + start_block: Option, + + /// Start building from the latest MMR block + #[arg(short = 'l', long, default_value_t = false)] + from_latest: bool, } #[tokio::main] @@ -40,14 +48,12 @@ async fn main() -> Result<(), Box> { let private_key = get_env_var("STARKNET_PRIVATE_KEY")?; let account_address = get_env_var("STARKNET_ACCOUNT_ADDRESS")?; - // Parse CLI arguments - let args = Args::parse(); - let starknet_provider = StarknetProvider::new(&rpc_url)?; let starknet_account = StarknetAccount::new(starknet_provider.provider(), &private_key, &account_address)?; let mut builder = AccumulatorBuilder::new( + &rpc_url, chain_id, &verifier_address, &store_address, @@ -61,11 +67,23 @@ async fn main() -> Result<(), Box> { e })?; - // Build MMR from finalized block to block #0 or up to the specified number of batches - if let Some(num_batches) = args.num_batches { - builder.build_with_num_batches(num_batches).await?; - } else { - builder.build_from_finalized().await?; + // Build MMR from specified start block or finalized block + match (args.from_latest, args.start_block, args.num_batches) { + (true, Some(_), _) => { + return Err("Cannot specify both --from-latest and --start-block".into()); + } + (true, None, Some(num_batches)) => { + builder.build_from_latest_with_batches(num_batches).await? + } + (true, None, None) => builder.build_from_latest().await?, + (false, Some(start_block), Some(num_batches)) => { + builder + .build_from_block_with_batches(start_block, num_batches) + .await? + } + (false, Some(start_block), None) => builder.build_from_block(start_block).await?, + (false, None, Some(num_batches)) => builder.build_with_num_batches(num_batches).await?, + (false, None, None) => builder.build_from_finalized().await?, } info!("Host finished"); diff --git a/crates/publisher/src/api/operations.rs b/crates/publisher/src/api/operations.rs index 5c0d601..08f7c95 100644 --- a/crates/publisher/src/api/operations.rs +++ b/crates/publisher/src/api/operations.rs @@ -26,6 +26,7 @@ pub async fn prove_mmr_update( )?; let mut builder = AccumulatorBuilder::new( + rpc_url, chain_id, verifier_address, store_address, diff --git a/crates/publisher/src/core/accumulator.rs b/crates/publisher/src/core/accumulator.rs index 75494e4..1b8c453 100644 --- a/crates/publisher/src/core/accumulator.rs +++ b/crates/publisher/src/core/accumulator.rs @@ -5,11 +5,13 @@ use ethereum::get_finalized_block_hash; use methods::{MMR_APPEND_ELF, MMR_APPEND_ID}; use starknet_crypto::Felt; use starknet_handler::account::StarknetAccount; +use starknet_handler::provider::StarknetProvider; use tracing::{debug, error, info, warn}; use super::MMRStateManager; pub struct AccumulatorBuilder<'a> { + starknet_rpc_url: &'a String, chain_id: u64, verifier_address: &'a String, batch_processor: BatchProcessor<'a>, @@ -19,6 +21,7 @@ pub struct AccumulatorBuilder<'a> { impl<'a> AccumulatorBuilder<'a> { pub async fn new( + starknet_rpc_url: &'a String, chain_id: u64, verifier_address: &'a String, store_address: &'a String, @@ -41,6 +44,7 @@ impl<'a> AccumulatorBuilder<'a> { } Ok(Self { + starknet_rpc_url, chain_id, verifier_address, batch_processor: BatchProcessor::new( @@ -240,7 +244,7 @@ impl<'a> AccumulatorBuilder<'a> { async fn verify_proof(&self, calldata: Vec) -> Result<(), AccumulatorError> { let starknet_account = self.batch_processor.mmr_state_manager().account(); - debug!("Verifying MMR proof"); + info!("Verifying MMR proof"); starknet_account .verify_mmr_proof(&self.verifier_address, calldata) .await @@ -249,7 +253,171 @@ impl<'a> AccumulatorBuilder<'a> { e })?; - debug!("MMR proof verified successfully"); + info!("MMR proof verified successfully"); Ok(()) } + + pub async fn build_from_block(&mut self, start_block: u64) -> Result<(), AccumulatorError> { + info!("Building MMR from block {}", start_block); + self.process_blocks_from(start_block).await + } + + pub async fn build_from_block_with_batches( + &mut self, + start_block: u64, + num_batches: u64, + ) -> Result<(), AccumulatorError> { + info!( + "Building MMR from block {} with {} batches", + start_block, num_batches + ); + self.process_blocks_from_with_limit(start_block, num_batches) + .await + } + + async fn process_blocks_from(&mut self, start_block: u64) -> Result<(), AccumulatorError> { + let (finalized_block_number, _) = get_finalized_block_hash().await?; + if start_block > finalized_block_number { + return Err(AccumulatorError::InvalidInput( + "Start block cannot be greater than finalized block", + )); + } + + debug!( + "Processing blocks from {} with batch size {}", + start_block, + self.batch_processor.batch_size() + ); + + let mut current_end = start_block; + + while current_end > 0 { + let start = self.batch_processor.calculate_start_block(current_end)?; + let batch_result = self + .batch_processor + .process_batch(self.chain_id, start, current_end) + .await?; + + if let Some(result) = batch_result { + self.handle_batch_result(&result).await?; + } + + current_end = start.saturating_sub(1); + } + + Ok(()) + } + + async fn process_blocks_from_with_limit( + &mut self, + start_block: u64, + num_batches: u64, + ) -> Result<(), AccumulatorError> { + if num_batches == 0 { + return Err(AccumulatorError::InvalidInput( + "Number of batches must be greater than 0", + )); + } + + let (finalized_block_number, _) = get_finalized_block_hash().await.map_err(|e| { + error!(error = %e, "Failed to get finalized block hash"); + AccumulatorError::BlockchainError(format!("Failed to get finalized block: {}", e)) + })?; + + if start_block > finalized_block_number { + return Err(AccumulatorError::InvalidInput( + "Start block cannot be greater than finalized block", + )); + } + + self.total_batches = num_batches; + self.current_batch = 0; + let mut current_end = start_block; + + for batch_num in 0..num_batches { + if current_end == 0 { + warn!("Reached block 0 before completing all batches"); + break; + } + + let start = self.batch_processor.calculate_start_block(current_end)?; + debug!(batch_num, start, current_end, "Processing batch"); + + let result = self + .batch_processor + .process_batch(self.chain_id, start, current_end) + .await + .map_err(|e| { + error!( + error = %e, + batch_num, + start, + current_end, + "Failed to process batch" + ); + e + })?; + + if let Some(batch_result) = result { + self.handle_batch_result(&batch_result).await?; + self.current_batch += 1; + info!( + progress = format!("{}/{}", self.current_batch, self.total_batches), + "Batch processed successfully" + ); + } + + current_end = start.saturating_sub(1); + } + + info!("MMR build completed successfully"); + Ok(()) + } + + pub async fn build_from_latest(&mut self) -> Result<(), AccumulatorError> { + let provider = StarknetProvider::new(&self.starknet_rpc_url).map_err(|e| { + error!(error = %e, "Failed to create Starknet provider"); + AccumulatorError::BlockchainError(format!("Failed to create Starknet provider: {}", e)) + })?; + + let latest_mmr_block = provider + .get_min_mmr_block(&self.batch_processor.mmr_state_manager().store_address()) + .await + .map_err(|e| { + error!(error = %e, "Failed to get latest MMR block"); + AccumulatorError::BlockchainError(format!("Failed to get latest MMR block: {}", e)) + })?; + + info!( + "Building MMR from minimum MMR block {} - 1", + latest_mmr_block + ); + self.process_blocks_from(latest_mmr_block - 1).await + } + + pub async fn build_from_latest_with_batches( + &mut self, + num_batches: u64, + ) -> Result<(), AccumulatorError> { + let provider = StarknetProvider::new(&self.starknet_rpc_url).map_err(|e| { + error!(error = %e, "Failed to create Starknet provider"); + AccumulatorError::BlockchainError(format!("Failed to create Starknet provider: {}", e)) + })?; + + let min_mmr_block = provider + .get_min_mmr_block(&self.batch_processor.mmr_state_manager().store_address()) + .await + .map_err(|e| { + error!(error = %e, "Failed to get minimum MMR block"); + AccumulatorError::BlockchainError(format!("Failed to get minimum MMR block: {}", e)) + })?; + + info!( + "Building MMR from latest MMR block {} with {} batches", + min_mmr_block - 1, + num_batches + ); + self.process_blocks_from_with_limit(min_mmr_block - 1, num_batches) + .await + } } diff --git a/crates/publisher/src/core/batch_processor.rs b/crates/publisher/src/core/batch_processor.rs index 0c6bc0f..5737007 100644 --- a/crates/publisher/src/core/batch_processor.rs +++ b/crates/publisher/src/core/batch_processor.rs @@ -77,7 +77,7 @@ impl<'a> BatchProcessor<'a> { info!( batch_index, - num_blocks = adjusted_end_block - start_block + 1, + num_blocks = adjusted_end_block - start_block, "Processing batch" ); @@ -151,7 +151,7 @@ impl<'a> BatchProcessor<'a> { let batch_link: Option = if batch_index > 0 { Some( db_connection - .get_block_header_by_number(batch_start - 1) + .get_block_header_by_number(start_block - 1) .await? .ok_or_else(|| { AccumulatorError::InvalidInput("Previous block header not found") @@ -207,7 +207,6 @@ impl<'a> BatchProcessor<'a> { store_manager, &mut mmr, &pool, - batch_index, adjusted_end_block, guest_output.as_ref(), &new_headers, diff --git a/crates/publisher/src/core/mmr_state_manager.rs b/crates/publisher/src/core/mmr_state_manager.rs index 9bab507..807d41d 100644 --- a/crates/publisher/src/core/mmr_state_manager.rs +++ b/crates/publisher/src/core/mmr_state_manager.rs @@ -33,7 +33,6 @@ impl<'a> MMRStateManager<'a> { store_manager: StoreManager, mmr: &mut MMR, pool: &SqlitePool, - batch_index: u64, latest_block_number: u64, guest_output: Option<&GuestOutput>, headers: &Vec, @@ -108,15 +107,7 @@ impl<'a> MMRStateManager<'a> { leaves_count as u64, ); - let tx_hash = self - .account - .update_mmr_state(self.store_address, batch_index, &new_mmr_state) - .await?; - - info!( - tx_hash = ?tx_hash, - "MMR state updated successfully (without verification)" - ); + info!("No verification option selected, MMR state not updated onchain"); Ok(new_mmr_state) } } diff --git a/crates/starknet-handler/Cargo.toml b/crates/starknet-handler/Cargo.toml index 488e2d1..d6afb5d 100644 --- a/crates/starknet-handler/Cargo.toml +++ b/crates/starknet-handler/Cargo.toml @@ -10,6 +10,7 @@ thiserror = { workspace = true } starknet = { workspace = true } tracing = { workspace = true } starknet-crypto = { workspace = true } +tokio = {workspace = true} crypto-bigint = "0.5.5" url = "2.5.4" \ No newline at end of file diff --git a/crates/starknet-handler/src/account.rs b/crates/starknet-handler/src/account.rs index f28fade..f3c6921 100644 --- a/crates/starknet-handler/src/account.rs +++ b/crates/starknet-handler/src/account.rs @@ -1,17 +1,17 @@ use starknet::macros::selector; use starknet::{ accounts::{Account, ExecutionEncoding, SingleOwnerAccount}, - core::{chain_id, codec::Encode}, + core::chain_id, providers::{jsonrpc::HttpTransport, JsonRpcClient}, signers::{LocalWallet, SigningKey}, }; use starknet_crypto::Felt; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use tracing::{debug, info, instrument, warn}; use common::felt; -use crate::{MmrState, StarknetHandlerError}; +use crate::StarknetHandlerError; pub struct StarknetAccount { account: SingleOwnerAccount>, LocalWallet>, @@ -56,54 +56,50 @@ impl StarknetAccount { verifier_address: &str, proof: Vec, ) -> Result { - debug!( - verifier_address = %verifier_address, - proof_length = proof.len(), - "Verifying MMR proof" - ); + const MAX_RETRIES: u32 = 3; + const INITIAL_BACKOFF: Duration = Duration::from_secs(1); let selector = selector!("verify_mmr_proof"); - - debug!("Executing verification transaction"); - let tx = self - .account - .execute_v1(vec![starknet::core::types::Call { - selector, - calldata: proof, - to: felt(verifier_address)?, - }]) - .send() - .await?; - - info!( - tx_hash = ?tx.transaction_hash, - "MMR proof onchain verification successful." - ); - Ok(tx.transaction_hash) - } - - pub async fn update_mmr_state( - &self, - store_address: &str, - batch_index: u64, - mmr_state: &MmrState, - ) -> Result { - let selector = selector!("update_mmr_state"); - - let mut calldata = vec![]; - calldata.push(Felt::from(batch_index)); - mmr_state.encode(&mut calldata)?; - - let tx = self - .account - .execute_v1(vec![starknet::core::types::Call { - selector, - calldata, - to: felt(store_address)?, - }]) - .send() - .await?; - - Ok(tx.transaction_hash) + let call = starknet::core::types::Call { + selector, + calldata: proof.clone(), + to: felt(verifier_address)?, + }; + + let mut attempt = 0; + loop { + debug!( + verifier_address = %verifier_address, + proof_length = proof.len(), + attempt = attempt + 1, + "Verifying MMR proof" + ); + + match self.account.execute_v1(vec![call.clone()]).send().await { + Ok(tx) => { + info!( + tx_hash = ?tx.transaction_hash, + "MMR proof onchain verification successful." + ); + return Ok(tx.transaction_hash); + } + Err(e) => { + if attempt >= MAX_RETRIES { + warn!("Max retries reached for MMR proof verification"); + return Err(e.into()); + } + + let backoff = INITIAL_BACKOFF * 2u32.pow(attempt); + warn!( + error = ?e, + retry_in = ?backoff, + "MMR proof verification failed, retrying..." + ); + + tokio::time::sleep(backoff).await; + attempt += 1; + } + } + } } } diff --git a/crates/starknet-handler/src/provider.rs b/crates/starknet-handler/src/provider.rs index a8cbc77..d645e31 100644 --- a/crates/starknet-handler/src/provider.rs +++ b/crates/starknet-handler/src/provider.rs @@ -67,6 +67,33 @@ impl StarknetProvider { Ok(mmr_block) } + #[instrument(skip(self), level = "debug")] + pub async fn get_min_mmr_block( + &self, + l2_store_address: &str, + ) -> Result { + debug!("Fetching min MMR block"); + + let entry_point_selector = selector!("get_min_mmr_block"); + + let data = self + .provider + .call( + FunctionCall { + contract_address: Felt::from_hex(l2_store_address)?, + entry_point_selector, + calldata: vec![], + }, + BlockId::Tag(BlockTag::Latest), + ) + .await?; + + let min_mmr_block = u64::decode(&data)?; + info!(min_mmr_block, "Retrieved minimum MMR block"); + + Ok(min_mmr_block) + } + #[instrument(skip(self), level = "debug")] pub async fn get_mmr_state( &self,