-
Notifications
You must be signed in to change notification settings - Fork 147
feat: Reduce PendingProofs disk usage by storing Bitcoin indices #3209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: nightly
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ use std::time::{Duration, Instant}; | |
| use anyhow::{anyhow, bail}; | ||
| use citrea_common::backup::BackupManager; | ||
| use citrea_common::cache::L1BlockCache; | ||
| use citrea_common::da::{extract_zk_proofs_and_sequencer_commitments, sync_l1, ProofOrCommitment}; | ||
| use citrea_common::da::{extract_zk_proofs_and_sequencer_commitments, sync_l1, ProofOrCommitment, ProofWithLocation}; | ||
| use citrea_common::utils::{ | ||
| exceeded_stop_height, get_tangerine_activation_height_non_zero, shutdown_requested, | ||
| }; | ||
|
|
@@ -591,17 +591,19 @@ where | |
| &self, | ||
| current_l1_block_height: u64, | ||
| found_in_l1_block_height: u64, | ||
| proof: Proof, | ||
| proof_with_location: ProofWithLocation, | ||
| proof_source: ProofSource, | ||
| ) -> Result<ProcessingResult, ProcessingError> { | ||
| let proof = &proof_with_location.proof; | ||
|
|
||
| tracing::info!( | ||
| "Processing zk proof at height: {}", | ||
| found_in_l1_block_height | ||
| ); | ||
| tracing::trace!("ZK proof: {:?}", proof); | ||
|
|
||
| // Extract and verify the proof using the appropriate ZKVM | ||
| let Ok(batch_proof_output) = Vm::extract_output::<BatchProofCircuitOutput>(&proof) else { | ||
| let Ok(batch_proof_output) = Vm::extract_output::<BatchProofCircuitOutput>(proof) else { | ||
| return Ok(ProcessingResult::Discarded); | ||
| }; | ||
|
|
||
|
|
@@ -635,7 +637,7 @@ where | |
| current_l1_block_height, | ||
| found_in_l1_block_height, | ||
| batch_proof_output.initial_state_root(), | ||
| proof, | ||
| proof_with_location, | ||
| batch_proof_output, | ||
| proof_source, | ||
| ) | ||
|
|
@@ -648,7 +650,7 @@ where | |
| /// * `current_l1_block_height` - Current L1 block being processed | ||
| /// * `found_in_l1_block_height` - L1 block where proof was found | ||
| /// * `initial_state_root` - Initial state root for verification | ||
| /// * `raw_proof` - The raw ZK proof | ||
| /// * `proof_with_location` - The ZK proof with Bitcoin location metadata | ||
| /// * `batch_proof_output` - The batch proof circuit output | ||
| /// * `proof_source` - Whether the proof came from L1 or pending retries | ||
| /// | ||
|
|
@@ -659,7 +661,7 @@ where | |
| current_l1_block_height: u64, | ||
| found_in_l1_block_height: u64, | ||
| initial_state_root: [u8; 32], | ||
| raw_proof: Proof, | ||
| proof_with_location: ProofWithLocation, | ||
| batch_proof_output: BatchProofCircuitOutput, | ||
| proof_source: ProofSource, | ||
| ) -> Result<ProcessingResult, ProcessingError> { | ||
|
|
@@ -733,15 +735,16 @@ where | |
| if proof_is_pending { | ||
| if proof_source == ProofSource::FromL1 { | ||
| info!( | ||
| "Proof is pending for commitment index range {}-{}. Storing proof as pending.", | ||
| "Proof is pending for commitment index range {}-{}. Storing proof location instead of full proof to reduce disk usage.", | ||
| sequencer_commitment_index_range.0, sequencer_commitment_index_range.1 | ||
| ); | ||
| self.ledger_db.store_pending_proof( | ||
| sequencer_commitment_index_range.0, | ||
| sequencer_commitment_index_range.1, | ||
| raw_proof, | ||
| proof_with_location.bitcoin_block_height, | ||
| proof_with_location.bitcoin_tx_index, | ||
| found_in_l1_block_height, | ||
| )?; | ||
| )? | ||
| } else { | ||
| info!( | ||
| "Proof is pending for commitment index range {}-{}. Keeping existing pending proof without rewrite.", | ||
|
|
@@ -773,7 +776,7 @@ where | |
| if sequencer_commitment_index_range.0 > proven_height.commitment_index + 1 { | ||
| if proof_source == ProofSource::FromL1 { | ||
| info!( | ||
| "First commitment in range is not strictly increasing. Expected index {}, got {}. Storing proof as pending for commitment range {}-{}", | ||
| "First commitment in range is not strictly increasing. Expected index {}, got {}. Storing proof location for commitment range {}-{}", | ||
| proven_height.commitment_index + 1, | ||
| sequencer_commitment_index_range.0, | ||
| sequencer_commitment_index_range.0, | ||
|
|
@@ -782,9 +785,10 @@ where | |
| self.ledger_db.store_pending_proof( | ||
| sequencer_commitment_index_range.0, | ||
| sequencer_commitment_index_range.1, | ||
| raw_proof, | ||
| proof_with_location.bitcoin_block_height, | ||
| proof_with_location.bitcoin_tx_index, | ||
| found_in_l1_block_height, | ||
| )?; | ||
| )? | ||
| } else { | ||
| info!( | ||
| "First commitment in range is not strictly increasing. Expected index {}, got {}. Keeping existing pending proof for commitment range {}-{} without rewrite", | ||
|
|
@@ -797,6 +801,7 @@ where | |
| return Ok(ProcessingResult::Pending); | ||
| } | ||
|
|
||
| let raw_proof = proof_with_location.proof.clone(); | ||
| // store in ledger db | ||
| self.ledger_db.update_verified_proof_data( | ||
| found_in_l1_block_height, | ||
|
|
@@ -915,12 +920,27 @@ where | |
| let pending_proofs = self.ledger_db.get_pending_proofs()?; | ||
|
|
||
| for item in pending_proofs { | ||
| let ((min_index, max_index), (proof, found_in_l1_height)) = item?.into_tuple(); | ||
| let ((min_index, max_index), (proof_location, found_in_l1_height)) = item?.into_tuple(); | ||
|
|
||
| // Fetch the proof from Bitcoin using the stored location | ||
| let proof_with_location = match self.fetch_proof_from_bitcoin(proof_location).await { | ||
| Ok(p) => p, | ||
| Err(e) => { | ||
| warn!( | ||
| "Failed to fetch proof from Bitcoin for index {min_index}-{max_index} at location block={}, tx_idx={}, found_in_l1_height={}: {e:?}", | ||
| proof_location.block_height, proof_location.tx_index, found_in_l1_height | ||
| ); | ||
| // Keep this entry pending for future retry but don't block processing of later proofs. | ||
| // Transient RPC failures may succeed on next cycle. | ||
| continue; | ||
| } | ||
| }; | ||
|
|
||
| match self | ||
| .process_zk_proof( | ||
| current_l1_block_height, | ||
| found_in_l1_height, | ||
| proof, | ||
| proof_with_location, | ||
| ProofSource::FromPendingRetry, | ||
| ) | ||
| .await | ||
|
|
@@ -995,4 +1015,62 @@ where | |
| } | ||
| Ok(sequencer_commitment.l2_end_block_number) | ||
| } | ||
|
|
||
| /// Fetches a proof from Bitcoin using stored location information | ||
| /// | ||
| /// This method retrieves a proof that was previously identified by its Bitcoin block height | ||
| /// and transaction index, reducing the need to store full proofs in the pending proofs table. | ||
| /// | ||
| /// # Arguments | ||
| /// * `proof_location` - Bitcoin block height and transaction index where proof is located | ||
| /// * `l1_height` - L1 block height for context | ||
| /// | ||
| /// # Returns | ||
| /// A ProofWithLocation containing the fetched proof and location metadata | ||
| async fn fetch_proof_from_bitcoin( | ||
| &self, | ||
| proof_location: sov_db::schema::types::BitcoinProofLocation, | ||
| ) -> Result<ProofWithLocation, ProcessingError> { | ||
|
Comment on lines
+1030
to
+1033
|
||
| // Fetch the Bitcoin block at the specified height | ||
| let block = self.da_service | ||
| .get_block_at(proof_location.block_height) | ||
| .await | ||
| .map_err(|e| { | ||
| ProcessingError::SkippableError( | ||
| SkippableError::Proof( | ||
| ProofError::ProofFetchFailed(format!( | ||
| "Failed to fetch Bitcoin block at height {}: {}", | ||
| proof_location.block_height, e | ||
| )) | ||
| ) | ||
| ) | ||
| })?; | ||
|
|
||
| // Extract proofs from the block | ||
| let proofs = self.da_service | ||
| .extract_relevant_zk_proofs(&block, &self.prover_da_pub_key) | ||
| .await; | ||
|
|
||
| // Find the proof at the specified transaction index | ||
| let proof = proofs | ||
| .into_iter() | ||
| .find(|(tx_idx, _)| *tx_idx == proof_location.tx_index as usize) | ||
| .map(|(_, proof)| proof) | ||
| .ok_or_else(|| { | ||
| ProcessingError::SkippableError( | ||
| SkippableError::Proof( | ||
| ProofError::ProofFetchFailed(format!( | ||
| "Proof not found at Bitcoin block {} tx index {}", | ||
| proof_location.block_height, proof_location.tx_index | ||
| )) | ||
| ) | ||
| ) | ||
| })?; | ||
|
|
||
| Ok(ProofWithLocation { | ||
| proof, | ||
| bitcoin_block_height: proof_location.block_height, | ||
| bitcoin_tx_index: proof_location.tx_index, | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,7 +34,7 @@ use crate::schema::types::light_client_proof::{ | |
| StoredLightClientProof, StoredLightClientProofOutput, | ||
| }; | ||
| use crate::schema::types::{ | ||
| BonsaiSession, BoundlessSession, L2BlockNumber, L2HeightAndIndex, L2HeightRange, | ||
| BitcoinProofLocation, BonsaiSession, BoundlessSession, L2BlockNumber, L2HeightAndIndex, L2HeightRange, | ||
| L2HeightStatus, SlotNumber, | ||
| }; | ||
|
|
||
|
|
@@ -979,13 +979,18 @@ impl NodeLedgerOps for LedgerDB { | |
| &self, | ||
| min_commitment_index: u32, | ||
| max_commitment_index: u32, | ||
| proof: Proof, | ||
| bitcoin_block_height: u64, | ||
| bitcoin_tx_index: u32, | ||
| found_in_l1_height: u64, | ||
| ) -> anyhow::Result<()> { | ||
| let mut schema_batch = SchemaBatch::new(); | ||
| let proof_location = BitcoinProofLocation { | ||
| block_height: bitcoin_block_height, | ||
| tx_index: bitcoin_tx_index, | ||
| }; | ||
| schema_batch.put::<PendingProofs>( | ||
| &(min_commitment_index, max_commitment_index), | ||
| &(proof, found_in_l1_height), | ||
| &(proof_location, found_in_l1_height), | ||
| )?; | ||
|
Comment on lines
+987
to
994
|
||
| self.db.write_schemas(schema_batch)?; | ||
| Ok(()) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On
fetch_proof_from_bitcoinfailure youbreakout of the loop but keep the (potentially permanently broken) pending-proof entry in the DB. If the failure is non-transient (e.g., tx index not found due to reorg/pruning), this can prevent all subsequent pending proofs from ever being retried/cleared. Consider distinguishing transient RPC failures vs permanent “not found” cases (e.g., remove/discard the pending entry on permanent failure, or at leastcontinueto allow later ranges to be processed).