diff --git a/bin/prover-client/src/operators/checkpoint.rs b/bin/prover-client/src/operators/checkpoint.rs index 8f0d44431..4ecc7d306 100644 --- a/bin/prover-client/src/operators/checkpoint.rs +++ b/bin/prover-client/src/operators/checkpoint.rs @@ -4,6 +4,8 @@ use jsonrpsee::http_client::HttpClient; use strata_db::traits::ProofDatabase; use strata_primitives::{ buf::Buf32, + l1::L1BlockCommitment, + l2::L2BlockCommitment, params::RollupParams, proof::{ProofContext, ProofKey}, }; @@ -80,19 +82,13 @@ impl CheckpointOperator { // Doing the manual block idx to id transformation. Will be removed once checkpoint_info // include the range in terms of block_id. // https://alpenlabs.atlassian.net/browse/STR-756 - let start_l1_block_id = self - .l1_batch_operator - .get_block_at(checkpoint_info.l1_range.0) - .await?; - let end_l1_block_id = self - .l1_batch_operator - .get_block_at(checkpoint_info.l1_range.1) - .await?; + let start_l1_block_id = checkpoint_info.l1_range.0.blkid(); + let end_l1_block_id = checkpoint_info.l1_range.1.blkid(); let l1_batch_keys = self .l1_batch_operator .create_task( - (start_l1_block_id, end_l1_block_id), + (*start_l1_block_id, *end_l1_block_id), task_tracker.clone(), db, ) @@ -102,9 +98,9 @@ impl CheckpointOperator { // Doing the manual block idx to id transformation. Will be removed once checkpoint_info // include the range in terms of block_id. // https://alpenlabs.atlassian.net/browse/STR-756 - let start_l2_idx = self.get_l2id(checkpoint_info.l2_range.0).await?; - let end_l2_idx = self.get_l2id(checkpoint_info.l2_range.1).await?; - let l2_range = vec![(start_l2_idx, end_l2_idx)]; + let start_l2_idx = checkpoint_info.l2_range.0.blkid(); + let end_l2_idx = checkpoint_info.l2_range.1.blkid(); + let l2_range = vec![(*start_l2_idx, *end_l2_idx)]; let l2_batch_keys = self .l2_batch_operator @@ -145,12 +141,25 @@ impl CheckpointOperator { task_tracker: Arc>, db: &ProofDb, ) -> Result, ProvingTaskError> { + let (start_l1_height, end_l1_height) = l1_range; + let (start_l2_height, end_l2_height) = l2_range; + + let start_l1_block_id = self.l1_batch_operator.get_block_at(start_l1_height).await?; + let start_l1_commitment = L1BlockCommitment::new(start_l1_height, start_l1_block_id); + + let end_l1_block_id = self.l1_batch_operator.get_block_at(end_l1_height).await?; + let end_l1_commitment = L1BlockCommitment::new(end_l1_height, end_l1_block_id); + + let start_l2_block_id = self.get_l2id(start_l2_height).await?; + let start_l2_commitment = L2BlockCommitment::new(start_l2_height, start_l2_block_id); + + let end_l2_block_id = self.get_l2id(end_l2_height).await?; + let end_l2_commitment = L2BlockCommitment::new(end_l2_height, end_l2_block_id); + let checkpoint_info = RpcCheckpointInfo { idx: checkpoint_idx, - l1_range, - l2_range, - // TODO: likely unused and should be removed. - l2_blockid: Buf32::default().into(), + l1_range: (start_l1_commitment, end_l1_commitment), + l2_range: (start_l2_commitment, end_l2_commitment), commitment: None, confirmation_status: None, }; diff --git a/bin/strata-client/src/rpc_server.rs b/bin/strata-client/src/rpc_server.rs index 46e4c0d64..7dff5ee52 100644 --- a/bin/strata-client/src/rpc_server.rs +++ b/bin/strata-client/src/rpc_server.rs @@ -116,12 +116,12 @@ impl StrataRpcImpl { }; // in current implementation, chainstate idx == l2 block idx - let (_, end_slot) = last_checkpoint.batch_info.l2_range; + let (_, end_commitment) = last_checkpoint.batch_info.l2_range; Ok(self .storage .chainstate() - .get_toplevel_chainstate_async(end_slot) + .get_toplevel_chainstate_async(end_commitment.slot()) .await? .map(Arc::new)) } @@ -552,7 +552,7 @@ impl StrataApiServer for StrataRpcImpl { Ok(client_state .l1_view() .last_finalized_checkpoint() - .map(|checkpoint| checkpoint.batch_info.idx())) + .map(|checkpoint| checkpoint.batch_info.epoch())) } else { // get latest checkpoint index from db let idx = self @@ -741,10 +741,10 @@ impl StrataSequencerApiServer for SequencerServerImpl { verify_proof(&checkpoint, &proof_receipt, self.params.rollup()) .map_err(|e| Error::InvalidProof(idx, e.to_string()))?; - entry.proof = proof_receipt; + entry.checkpoint.update_proof(proof_receipt.proof().clone()); entry.proving_status = CheckpointProvingStatus::ProofReady; - debug!(%idx, "Proof is pending, setting proof reaedy"); + debug!(%idx, "Proof is pending, setting proof ready"); self.checkpoint_handle .put_checkpoint(idx, entry) diff --git a/bin/strata-sequencer-client/src/duty_executor.rs b/bin/strata-sequencer-client/src/duty_executor.rs index 39d451171..dfadd5180 100644 --- a/bin/strata-sequencer-client/src/duty_executor.rs +++ b/bin/strata-sequencer-client/src/duty_executor.rs @@ -125,9 +125,9 @@ async fn handle_commit_batch_duty( where R: StrataSequencerApiClient + Send + Sync, { - let sig = sign_checkpoint(duty.checkpoint(), &idata.key); + let sig = sign_checkpoint(duty.inner(), &idata.key); - rpc.complete_checkpoint_signature(duty.checkpoint().batch_info().idx(), HexBytes64(sig.0)) + rpc.complete_checkpoint_signature(duty.inner().batch_info().epoch(), HexBytes64(sig.0)) .await .map_err(DutyExecError::CompleteCheckpoint)?; diff --git a/crates/consensus-logic/src/csm/client_transition.rs b/crates/consensus-logic/src/csm/client_transition.rs index a034ca1be..38378b910 100644 --- a/crates/consensus-logic/src/csm/client_transition.rs +++ b/crates/consensus-logic/src/csm/client_transition.rs @@ -206,7 +206,8 @@ pub fn process_event( &batch_checkpoint_with_commitment.batch_checkpoint; L1Checkpoint::new( batch_checkpoint.batch_info().clone(), - batch_checkpoint.bootstrap_state().clone(), + batch_checkpoint.batch_transition().clone(), + batch_checkpoint.base_state_commitment().clone(), !batch_checkpoint.proof().is_empty(), *height, ) @@ -311,7 +312,7 @@ fn handle_mature_l1_height( // If l2 blocks is not in db then finalization will happen when // l2Block is fetched from the network and the corresponding //checkpoint is already finalized. - let blkid = checkpt.batch_info.l2_blockid; + let blkid = *checkpt.batch_info.final_l2_blockid(); match context.get_l2_block_data(&blkid) { Ok(_) => { @@ -402,7 +403,12 @@ fn find_l1_height_for_l2_blockid( target_l2_blockid: &L2BlockId, ) -> Option { checkpoints - .binary_search_by(|checkpoint| checkpoint.batch_info.l2_blockid.cmp(target_l2_blockid)) + .binary_search_by(|checkpoint| { + checkpoint + .batch_info + .final_l2_blockid() + .cmp(target_l2_blockid) + }) .ok() .map(|index| checkpoints[index].height) } @@ -437,14 +443,14 @@ pub fn filter_verified_checkpoints( } else { last_finalized } - .map(|x| (x.batch_info.idx() + 1, Some(&x.batch_info))) + .map(|x| (x.batch_info.epoch() + 1, Some(&x.batch_transition))) .unwrap_or((0, None)); // expect the first checkpoint let mut result_checkpoints = Vec::new(); for checkpoint in checkpoints { - let curr_idx = checkpoint.batch_checkpoint.batch_info().idx; - let proof_receipt: ProofReceipt = checkpoint.batch_checkpoint.clone().into_proof_receipt(); + let curr_idx = checkpoint.batch_checkpoint.batch_info().epoch; + let proof_receipt: ProofReceipt = checkpoint.batch_checkpoint.get_proof_receipt(); if curr_idx != expected_idx { warn!(%expected_idx, %curr_idx, "Received invalid checkpoint idx, ignoring."); continue; @@ -453,7 +459,7 @@ pub fn filter_verified_checkpoints( && verify_proof(&checkpoint.batch_checkpoint, &proof_receipt, params).is_ok() { result_checkpoints.push(checkpoint.clone()); - last_valid_checkpoint = Some(checkpoint.batch_checkpoint.batch_info()); + last_valid_checkpoint = Some(checkpoint.batch_checkpoint.batch_transition()); } else if expected_idx == 0 { warn!(%expected_idx, "Received invalid checkpoint proof, ignoring."); } else { @@ -463,8 +469,8 @@ pub fn filter_verified_checkpoints( let last_l2_tsn = last_valid_checkpoint .expect("There should be a last_valid_checkpoint") .l2_transition; - let l1_tsn = checkpoint.batch_checkpoint.batch_info().l1_transition; - let l2_tsn = checkpoint.batch_checkpoint.batch_info().l2_transition; + let l1_tsn = checkpoint.batch_checkpoint.batch_transition().l1_transition; + let l2_tsn = checkpoint.batch_checkpoint.batch_transition().l2_transition; if l1_tsn.0 != last_l1_tsn.1 { warn!(obtained = ?l1_tsn.0, expected = ?last_l1_tsn.1, "Received invalid checkpoint l1 transition, ignoring."); @@ -476,7 +482,7 @@ pub fn filter_verified_checkpoints( } if verify_proof(&checkpoint.batch_checkpoint, &proof_receipt, params).is_ok() { result_checkpoints.push(checkpoint.clone()); - last_valid_checkpoint = Some(checkpoint.batch_checkpoint.batch_info()); + last_valid_checkpoint = Some(checkpoint.batch_checkpoint.batch_transition()); } else { warn!(%expected_idx, "Received invalid checkpoint proof, ignoring."); continue; diff --git a/crates/consensus-logic/src/csm/worker.rs b/crates/consensus-logic/src/csm/worker.rs index 6745fd30d..3ffa89ba6 100644 --- a/crates/consensus-logic/src/csm/worker.rs +++ b/crates/consensus-logic/src/csm/worker.rs @@ -360,13 +360,11 @@ fn apply_action( SyncAction::WriteCheckpoints(_height, checkpoints) => { for c in checkpoints.iter() { let batch_ckp = &c.batch_checkpoint; - let idx = batch_ckp.batch_info().idx(); + let idx = batch_ckp.batch_info().epoch(); let pstatus = CheckpointProvingStatus::ProofReady; let cstatus = CheckpointConfStatus::Confirmed; let entry = CheckpointEntry::new( - batch_ckp.batch_info().clone(), - batch_ckp.bootstrap_state().clone(), - batch_ckp.clone().into_proof_receipt(), + batch_ckp.clone(), pstatus, cstatus, Some(c.commitment.clone().into()), @@ -380,13 +378,11 @@ fn apply_action( SyncAction::FinalizeCheckpoints(_height, checkpoints) => { for c in checkpoints.iter() { let batch_ckp = &c.batch_checkpoint; - let idx = batch_ckp.batch_info().idx(); + let idx = batch_ckp.batch_info().epoch(); let pstatus = CheckpointProvingStatus::ProofReady; let cstatus = CheckpointConfStatus::Finalized; let entry = CheckpointEntry::new( - batch_ckp.batch_info().clone(), - batch_ckp.bootstrap_state().clone(), - batch_ckp.clone().into_proof_receipt(), + batch_ckp.clone(), pstatus, cstatus, Some(c.commitment.clone().into()), diff --git a/crates/consensus-logic/src/duty/worker.rs b/crates/consensus-logic/src/duty/worker.rs index f1c3fe750..7ff07a97c 100644 --- a/crates/consensus-logic/src/duty/worker.rs +++ b/crates/consensus-logic/src/duty/worker.rs @@ -159,7 +159,7 @@ fn update_tracker( let latest_finalized_batch = state .l1_view() .last_finalized_checkpoint() - .map(|x| x.batch_info.idx()); + .map(|x| x.batch_info.epoch()); let tracker_update = types::StateUpdate::new( block_idx, diff --git a/crates/consensus-logic/src/l1_handler.rs b/crates/consensus-logic/src/l1_handler.rs index cad592a6e..7ec59c609 100644 --- a/crates/consensus-logic/src/l1_handler.rs +++ b/crates/consensus-logic/src/l1_handler.rs @@ -184,7 +184,7 @@ pub fn verify_proof( rollup_params: &RollupParams, ) -> ZkVmResult<()> { let rollup_vk = rollup_params.rollup_vk; - let checkpoint_idx = checkpoint.batch_info().idx(); + let checkpoint_idx = checkpoint.batch_info().epoch(); info!(%checkpoint_idx, "verifying proof"); // FIXME: we are accepting empty proofs for now (devnet) to reduce dependency on the prover @@ -197,7 +197,7 @@ pub fn verify_proof( return Ok(()); } - let expected_public_output = checkpoint.proof_output(); + let expected_public_output = checkpoint.get_proof_output(); let actual_public_output: CheckpointProofOutput = borsh::from_slice(proof_receipt.public_values().as_bytes()) .map_err(|e| ZkVmError::OutputExtractionError { source: e.into() })?; diff --git a/crates/db/src/types.rs b/crates/db/src/types.rs index f26f85e39..2b40b165a 100644 --- a/crates/db/src/types.rs +++ b/crates/db/src/types.rs @@ -11,8 +11,10 @@ use strata_primitives::{ buf::Buf32, l1::payload::{L1Payload, PayloadIntent}, }; -use strata_state::batch::{BatchCheckpoint, BatchInfo, BootstrapState, CommitmentInfo}; -use zkaleido::ProofReceipt; +use strata_state::batch::{ + BaseStateCommitment, BatchCheckpoint, BatchInfo, BatchTransition, CommitmentInfo, +}; +use zkaleido::Proof; /// Represents an intent to publish to some DA, which will be bundled for efficiency. #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, Arbitrary)] @@ -184,15 +186,8 @@ pub enum L1TxStatus { /// Entry corresponding to a BatchCommitment #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, Arbitrary)] pub struct CheckpointEntry { - /// Info related to the batch - pub batch_info: BatchInfo, - - /// Includes the initial and final hashed state of both the `L1StateTransition` and - /// `L2StateTransition` that happened in this batch - pub bootstrap: BootstrapState, - - /// Proof with public values - pub proof: ProofReceipt, + /// The batch checkpoint containing metadata, state transitions, and proof data. + pub checkpoint: BatchCheckpoint, /// Proving Status pub proving_status: CheckpointProvingStatus, @@ -206,17 +201,13 @@ pub struct CheckpointEntry { impl CheckpointEntry { pub fn new( - batch_info: BatchInfo, - bootstrap: BootstrapState, - proof: ProofReceipt, + checkpoint: BatchCheckpoint, proving_status: CheckpointProvingStatus, confirmation_status: CheckpointConfStatus, commitment: Option, ) -> Self { Self { - batch_info, - bootstrap, - proof, + checkpoint, proving_status, confirmation_status, commitment, @@ -224,15 +215,19 @@ impl CheckpointEntry { } pub fn into_batch_checkpoint(self) -> BatchCheckpoint { - BatchCheckpoint::new(self.batch_info, self.bootstrap, self.proof.proof().clone()) + self.checkpoint } /// Creates a new instance for a freshly defined checkpoint. - pub fn new_pending_proof(info: BatchInfo, bootstrap: BootstrapState) -> Self { + pub fn new_pending_proof( + info: BatchInfo, + transition: BatchTransition, + base_state_commitment: BaseStateCommitment, + ) -> Self { + let checkpoint = + BatchCheckpoint::new(info, transition, base_state_commitment, Proof::default()); Self::new( - info, - bootstrap, - ProofReceipt::default(), + checkpoint, CheckpointProvingStatus::PendingProof, CheckpointConfStatus::Pending, None, @@ -269,6 +264,7 @@ pub enum CheckpointConfStatus { Finalized, } +// TODO: why is this needed? can this information be part of `L1Checkpoint`? #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, Arbitrary)] pub struct CheckpointCommitment { pub blockhash: Buf32, diff --git a/crates/primitives/src/l1/block.rs b/crates/primitives/src/l1/block.rs index b0d0089e0..cba58f43e 100644 --- a/crates/primitives/src/l1/block.rs +++ b/crates/primitives/src/l1/block.rs @@ -46,6 +46,7 @@ impl From for BlockHash { } #[derive( + Debug, Copy, Clone, Eq, diff --git a/crates/proof-impl/checkpoint/src/lib.rs b/crates/proof-impl/checkpoint/src/lib.rs index 42a9db3f7..48925e841 100644 --- a/crates/proof-impl/checkpoint/src/lib.rs +++ b/crates/proof-impl/checkpoint/src/lib.rs @@ -6,7 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use strata_primitives::{params::RollupParams, proof::RollupVerifyingKey}; use strata_proofimpl_cl_stf::L2BatchProofOutput; use strata_proofimpl_l1_batch::L1BatchProofOutput; -use strata_state::batch::{BatchInfo, CheckpointProofOutput}; +use strata_state::batch::{BatchTransition, CheckpointProofOutput}; use zkaleido::{ProofReceipt, ZkVmEnv}; pub mod prover; @@ -38,65 +38,53 @@ pub fn process_checkpoint_proof( ); // Create BatchInfo based on `l1_batch` and `l2_batch` - let mut batch_info = BatchInfo::new( - 0, + let batch_transition = BatchTransition::new( ( - l1_batch_output.initial_snapshot.block_num, - l1_batch_output.final_snapshot.block_num, + l1_batch_output.initial_state_hash, + l1_batch_output.final_state_hash, ), ( - l2_batch_output.initial_snapshot.slot, - l2_batch_output.final_snapshot.slot, - ), - ( - l1_batch_output.initial_snapshot.hash, - l1_batch_output.final_snapshot.hash, - ), - ( - l2_batch_output.initial_snapshot.hash, - l2_batch_output.final_snapshot.hash, - ), - l2_batch_output.final_snapshot.l2_blockid, - ( - l1_batch_output.initial_snapshot.acc_pow, - l1_batch_output.final_snapshot.acc_pow, + l2_batch_output.initial_state_hash, + l2_batch_output.final_state_hash, ), l1_batch_output.rollup_params_commitment, ); - let (bootstrap, opt_prev_output) = match l1_batch_output.prev_checkpoint.as_ref() { - // Genesis batch: initialize with initial bootstrap state - None => (batch_info.get_initial_bootstrap_state(), None), + let (base_state_commitment, opt_prev_output) = match l1_batch_output.prev_checkpoint.as_ref() { + // Genesis batch: initialize with initial base_state_commitment state + None => (batch_transition.get_initial_base_state_commitment(), None), Some(prev_checkpoint) => { // Ensure sequential state transition assert_eq!( - prev_checkpoint.batch_info().get_final_bootstrap_state(), - batch_info.get_initial_bootstrap_state() + prev_checkpoint + .batch_transition() + .get_final_base_state_commitment(), + batch_transition.get_initial_base_state_commitment() ); assert_eq!( - prev_checkpoint.batch_info().rollup_params_commitment(), - batch_info.rollup_params_commitment() + prev_checkpoint + .batch_transition() + .rollup_params_commitment(), + batch_transition.rollup_params_commitment() ); - batch_info.idx = prev_checkpoint.batch_info().idx + 1; - - // If there exist proof for the prev_batch, use the prev_batch bootstrap state, else set - // the current batch initial info as bootstrap + // If there exist proof for the prev_batch, use the prev_batch base_state_commitment + // state, else set the current batch initial info as base_state_commitment if prev_checkpoint.proof().is_empty() { - // No proof in previous checkpoint: use initial bootstrap state - (batch_info.get_initial_bootstrap_state(), None) + // No proof in previous checkpoint: use initial base_state_commitment state + (batch_transition.get_initial_base_state_commitment(), None) } else { - // Use previous checkpoint's bootstrap state and include previous proof - let bootstrap = prev_checkpoint.bootstrap_state().clone(); + // Use previous checkpoint's base_state_commitment state and include previous proof + let base_state_commitment = prev_checkpoint.base_state_commitment().clone(); ( - bootstrap, - Some(prev_checkpoint.clone().into_proof_receipt()), + base_state_commitment, + Some(prev_checkpoint.get_proof_receipt()), ) } } }; - let output = CheckpointProofOutput::new(batch_info, bootstrap); + let output = CheckpointProofOutput::new(batch_transition, base_state_commitment); (output, opt_prev_output) } diff --git a/crates/proof-impl/cl-agg/src/lib.rs b/crates/proof-impl/cl-agg/src/lib.rs index dd3e81d4f..8ceda7fa9 100644 --- a/crates/proof-impl/cl-agg/src/lib.rs +++ b/crates/proof-impl/cl-agg/src/lib.rs @@ -11,11 +11,7 @@ pub fn process_cl_agg(zkvm: &impl ZkVmEnv, cl_stf_vk: &[u32; 8]) { "At least one CL proof is required for aggregation" ); - let mut cl_proof_pp_start: L2BatchProofOutput = zkvm.read_verified_borsh(cl_stf_vk); - // `BatchInfo` has range which is inclusive. This makes it compatible and avoids off by 1 issue. - // TODO: Do this in a better way - cl_proof_pp_start.initial_snapshot.slot += 1; - + let cl_proof_pp_start: L2BatchProofOutput = zkvm.read_verified_borsh(cl_stf_vk); let mut cl_proof_pp_prev = cl_proof_pp_start.clone(); let mut acc_deposits = cl_proof_pp_start.deposits.clone(); @@ -36,8 +32,8 @@ pub fn process_cl_agg(zkvm: &impl ZkVmEnv, cl_stf_vk: &[u32; 8]) { // proof of the batch let public_params = L2BatchProofOutput { deposits: acc_deposits, - initial_snapshot: cl_proof_pp_start.initial_snapshot, - final_snapshot: cl_proof_pp_prev.final_snapshot, + initial_state_hash: cl_proof_pp_start.initial_state_hash, + final_state_hash: cl_proof_pp_prev.final_state_hash, rollup_params_commitment, }; @@ -50,8 +46,8 @@ fn validate_proof_consistency( next_proof_cs_snap: &L2BatchProofOutput, ) { assert_eq!( - current_proof_cs_snap.final_snapshot.hash, // post-state root of the current proof - next_proof_cs_snap.initial_snapshot.hash, // initial state root of the next proof + current_proof_cs_snap.final_state_hash, // post-state root of the current proof + next_proof_cs_snap.initial_state_hash, // initial state root of the next proof "State root mismatch between proofs" ); } diff --git a/crates/proof-impl/cl-stf/src/lib.rs b/crates/proof-impl/cl-stf/src/lib.rs index bf5648bed..0986ec0a1 100644 --- a/crates/proof-impl/cl-stf/src/lib.rs +++ b/crates/proof-impl/cl-stf/src/lib.rs @@ -8,24 +8,16 @@ use strata_primitives::{buf::Buf32, params::RollupParams}; use strata_state::{ block::ExecSegment, block_validation::{check_block_credential, validate_block_segments}, - id::L2BlockId, tx::DepositInfo, }; pub use strata_state::{block::L2Block, chain_state::Chainstate, state_op::StateCache}; use zkaleido::ZkVmEnv; -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] -pub struct ChainStateSnapshot { - pub hash: Buf32, - pub slot: u64, - pub l2_blockid: L2BlockId, -} - #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct L2BatchProofOutput { pub deposits: Vec, - pub initial_snapshot: ChainStateSnapshot, - pub final_snapshot: ChainStateSnapshot, + pub initial_state_hash: Buf32, + pub final_state_hash: Buf32, pub rollup_params_commitment: Buf32, } @@ -95,23 +87,11 @@ fn process_cl_stf( let new_state = verify_and_transition(prev_state.clone(), new_block, exec_update, rollup_params); - let initial_snapshot = ChainStateSnapshot { - hash: prev_state.compute_state_root(), - slot: prev_state.chain_tip_slot(), - l2_blockid: *prev_state.chain_tip_blkid(), - }; - - let final_snapshot = ChainStateSnapshot { - hash: new_state.compute_state_root(), - slot: new_state.chain_tip_slot(), - l2_blockid: *new_state.chain_tip_blkid(), - }; - L2BatchProofOutput { // TODO: Accumulate the deposits deposits: Vec::new(), - initial_snapshot, - final_snapshot, + initial_state_hash: prev_state.compute_state_root(), + final_state_hash: new_state.compute_state_root(), rollup_params_commitment: *rollup_params_commitment, } } @@ -152,7 +132,7 @@ pub fn batch_process_cl_stf(zkvm: &impl ZkVmEnv, el_vkey: &[u32; 8]) { ); assert_eq!( - cl_update.initial_snapshot.hash, cl_update_acc.final_snapshot.hash, + cl_update.initial_state_hash, cl_update_acc.final_state_hash, "Snapshot hash mismatch between consecutive updates." ); @@ -162,8 +142,8 @@ pub fn batch_process_cl_stf(zkvm: &impl ZkVmEnv, el_vkey: &[u32; 8]) { let output = L2BatchProofOutput { deposits, - initial_snapshot: initial_cl_update.initial_snapshot, - final_snapshot: cl_update_acc.final_snapshot, + initial_state_hash: initial_cl_update.initial_state_hash, + final_state_hash: cl_update_acc.final_state_hash, rollup_params_commitment: cl_update_acc.rollup_params_commitment, }; diff --git a/crates/proof-impl/l1-batch/src/logic.rs b/crates/proof-impl/l1-batch/src/logic.rs index aad48d297..09983c704 100644 --- a/crates/proof-impl/l1-batch/src/logic.rs +++ b/crates/proof-impl/l1-batch/src/logic.rs @@ -5,7 +5,7 @@ use strata_primitives::{buf::Buf32, params::RollupParams}; use strata_proofimpl_btc_blockspace::scan::process_blockscan; use strata_state::{ batch::BatchCheckpoint, - l1::{get_btc_params, HeaderVerificationState, HeaderVerificationStateSnapshot, L1TxProof}, + l1::{get_btc_params, HeaderVerificationState, L1TxProof}, tx::DepositInfo, }; use zkaleido::ZkVmEnv; @@ -15,8 +15,8 @@ use zkaleido::ZkVmEnv; pub struct L1BatchProofOutput { pub deposits: Vec, pub prev_checkpoint: Option, - pub initial_snapshot: HeaderVerificationStateSnapshot, - pub final_snapshot: HeaderVerificationStateSnapshot, + pub initial_state_hash: Buf32, + pub final_state_hash: Buf32, pub rollup_params_commitment: Buf32, } @@ -31,12 +31,12 @@ pub fn process_l1_batch_proof(zkvm: &impl ZkVmEnv) { let rollup_params: RollupParams = zkvm.read_serde(); let filter_config = - TxFilterConfig::derive_from(&rollup_params).expect("derive tx-filter config"); + TxFilterConfig::derive_from(&rollup_params).expect("failed to derive tx-filter config"); let num_inputs: u32 = zkvm.read_serde(); assert!(num_inputs > 0); - let initial_snapshot = state.compute_initial_snapshot(); + let initial_state_hash = state.compute_hash().expect("failed to compute state hash"); let mut deposits = Vec::new(); let mut prev_checkpoint = None; @@ -51,13 +51,13 @@ pub fn process_l1_batch_proof(zkvm: &impl ZkVmEnv) { deposits.extend(blockscan_result.deposits); prev_checkpoint = prev_checkpoint.or(blockscan_result.prev_checkpoint); } - let final_snapshot = state.compute_final_snapshot(); + let final_state_hash = state.compute_hash().expect("failed to compute state hash"); let output = L1BatchProofOutput { deposits, prev_checkpoint, - initial_snapshot, - final_snapshot, + initial_state_hash, + final_state_hash, rollup_params_commitment: rollup_params.compute_hash(), }; diff --git a/crates/rpc/types/src/types.rs b/crates/rpc/types/src/types.rs index 359dea499..22ab198ae 100644 --- a/crates/rpc/types/src/types.rs +++ b/crates/rpc/types/src/types.rs @@ -9,7 +9,8 @@ use serde::{Deserialize, Serialize}; use strata_db::types::{CheckpointCommitment, CheckpointConfStatus, CheckpointEntry}; use strata_primitives::{ bridge::OperatorIdx, - l1::{BitcoinAmount, L1TxRef, OutputRef}, + l1::{BitcoinAmount, L1BlockCommitment, L1TxRef, OutputRef}, + l2::L2BlockCommitment, prelude::L1Status, }; use strata_state::{ @@ -292,15 +293,10 @@ impl From for RpcCheckpointConfStatus { pub struct RpcCheckpointInfo { /// The index of the checkpoint pub idx: u64, - /// L1 height the checkpoint covers - /// TODO: use std::ops::Range - pub l1_range: (u64, u64), - /// L2 height the checkpoint covers - /// TODO: use std::ops::Range - pub l2_range: (u64, u64), - /// L2 block that this checkpoint covers - /// TODO: likely unused and should be removed. - pub l2_blockid: L2BlockId, + /// L1 range the checkpoint covers + pub l1_range: (L1BlockCommitment, L1BlockCommitment), + /// L2 range the checkpoint covers + pub l2_range: (L2BlockCommitment, L2BlockCommitment), /// Info on txn where checkpoint is committed on chain pub commitment: Option, /// Confirmation status of checkpoint @@ -310,10 +306,9 @@ pub struct RpcCheckpointInfo { impl From for RpcCheckpointInfo { fn from(value: BatchInfo) -> Self { Self { - idx: value.idx, + idx: value.epoch, l1_range: value.l1_range, l2_range: value.l2_range, - l2_blockid: value.l2_blockid, commitment: None, confirmation_status: None, } @@ -322,7 +317,7 @@ impl From for RpcCheckpointInfo { impl From for RpcCheckpointInfo { fn from(value: CheckpointEntry) -> Self { - let mut item: Self = value.batch_info.into(); + let mut item: Self = value.checkpoint.batch_info().clone().into(); item.commitment = value.commitment.map(Into::into); item.confirmation_status = Some(value.confirmation_status.into()); item diff --git a/crates/sequencer/src/checkpoint/worker.rs b/crates/sequencer/src/checkpoint/worker.rs index 04e25fe2c..7a6836358 100644 --- a/crates/sequencer/src/checkpoint/worker.rs +++ b/crates/sequencer/src/checkpoint/worker.rs @@ -3,10 +3,10 @@ use std::sync::Arc; use strata_consensus_logic::csm::message::ClientUpdateNotif; -use strata_db::{traits::Database, types::CheckpointEntry}; -use strata_primitives::{buf::Buf32, params::Params}; +use strata_db::{traits::Database, types::CheckpointEntry, DbError}; +use strata_primitives::{buf::Buf32, l1::L1BlockCommitment, l2::L2BlockCommitment, params::Params}; use strata_state::{ - batch::{BatchInfo, BootstrapState}, + batch::{BaseStateCommitment, BatchInfo, BatchTransition}, client_state::ClientState, }; use strata_storage::NodeStorage; @@ -68,22 +68,23 @@ pub fn checkpoint_worker( continue; } - let (batch_info, bootstrap_state) = + let (batch_info, batch_transition, base_state_commitment) = match get_next_batch(state, storage.as_ref(), rollup_params_commitment) { Err(error) => { warn!(?error, "Failed to get next batch"); continue; } - Ok((b, bs)) => (b, bs), + Ok((b, bt, bs)) => (b, bt, bs), }; - let checkpoint_idx = batch_info.idx(); + let checkpoint_idx = batch_info.epoch(); // sanity check assert!(checkpoint_idx == next_checkpoint_idx); // else save a pending proof checkpoint entry debug!("save checkpoint pending proof: {}", checkpoint_idx); - let entry = CheckpointEntry::new_pending_proof(batch_info, bootstrap_state); + let entry = + CheckpointEntry::new_pending_proof(batch_info, batch_transition, base_state_commitment); if let Err(e) = checkpoint_handle.put_checkpoint_and_notify_blocking(checkpoint_idx, entry) { warn!(?e, "Failed to save checkpoint at idx: {}", checkpoint_idx); @@ -95,7 +96,7 @@ pub fn checkpoint_worker( fn get_next_batch_idx(state: &ClientState) -> u64 { match state.l1_view().last_finalized_checkpoint() { None => 0, - Some(prev_checkpoint) => prev_checkpoint.batch_info.idx + 1, + Some(prev_checkpoint) => prev_checkpoint.batch_info.epoch + 1, } } @@ -103,128 +104,129 @@ fn get_next_batch( state: &ClientState, storage: &NodeStorage, rollup_params_commitment: Buf32, -) -> Result<(BatchInfo, BootstrapState), Error> { - let chsman = storage.chainstate(); - +) -> Result<(BatchInfo, BatchTransition, BaseStateCommitment), Error> { if !state.is_chain_active() { - // before genesis debug!("chain not active, no duties created"); return Err(Error::ChainInactive); - }; - - let Some(sync_state) = state.sync() else { - return Err(Error::ChainInactive); - }; + } + let sync_state = state.sync().ok_or(Error::ChainInactive)?; let tip_height = sync_state.chain_tip_height(); let tip_id = *sync_state.chain_tip_blkid(); - // Still not ready to make a batch? This should be only if the epoch is - // something else. if tip_height == 0 { return Err(Error::ChainInactive); } + let chsman = storage.chainstate(); + + // Fetch the current L1 verification state (required in both branches). + let current_l1_state = state + .l1_view() + .tip_verification_state() + .ok_or(Error::ChainInactive)?; + let current_l1_state_hash = current_l1_state.compute_hash().unwrap(); + + // Helper closures to get L1 and L2 block commitments. + let get_l1_commitment = |height: u64| -> Result { + let manifest = storage + .l1() + .get_block_manifest(height)? + .ok_or(DbError::MissingL1BlockBody(height))?; + Ok(L1BlockCommitment::new(height, manifest.block_hash())) + }; + + let get_l2_commitment = |height: u64| -> Result { + let blocks = storage.l2().get_blocks_at_height_blocking(height)?; + let block_id = blocks.first().ok_or(DbError::MissingL2State(height))?; + Ok(L2BlockCommitment::new(height, *block_id)) + }; + match state.l1_view().last_finalized_checkpoint() { - // Cool, we are producing first batch! + // --- Branch: First batch (no finalized checkpoint exists yet) --- None => { debug!( ?tip_height, ?tip_id, - "No finalized checkpoint, creating new checkpiont" + "No finalized checkpoint, creating new checkpoint" ); - let first_checkpoint_idx = 0; - let current_l1_state = state - .l1_view() - .tip_verification_state() - .ok_or(Error::ChainInactive)?; - - // Include blocks after genesis l1 height to last verified height - let l1_range = ( - state.genesis_l1_height() + 1, - current_l1_state.last_verified_block_num as u64, - ); let genesis_l1_state_hash = state .genesis_verification_hash() .ok_or(Error::ChainInactive)?; - let current_l1_state_hash = current_l1_state.compute_hash().unwrap(); + + // Determine the L1 range. + let initial_l1_height = state.genesis_l1_height() + 1; + let initial_l1_commitment = get_l1_commitment(initial_l1_height)?; + let final_l1_height = current_l1_state.last_verified_block_num as u64; + let final_l1_commitment = get_l1_commitment(final_l1_height)?; + let l1_range = (initial_l1_commitment, final_l1_commitment); let l1_transition = (genesis_l1_state_hash, current_l1_state_hash); - // Start from first non-genesis l2 block height - let l2_range = (1, tip_height); + // Determine the L2 range. + let initial_l2_height = 1; + let initial_l2_commitment = get_l2_commitment(initial_l2_height)?; + let final_l2_commitment = L2BlockCommitment::new(tip_height, tip_id); + let l2_range = (initial_l2_commitment, final_l2_commitment); + // Compute the L2 chainstate transition. let initial_chain_state = chsman .get_toplevel_chainstate_blocking(0)? .ok_or(Error::MissingIdxChainstate(0))?; let initial_chain_state_root = initial_chain_state.compute_state_root(); - let current_chain_state = chsman .get_toplevel_chainstate_blocking(tip_height)? - .ok_or(Error::MissingIdxChainstate(0))?; + .ok_or(Error::MissingIdxChainstate(tip_height))?; let current_chain_state_root = current_chain_state.compute_state_root(); let l2_transition = (initial_chain_state_root, current_chain_state_root); - let new_batch = BatchInfo::new( - first_checkpoint_idx, - l1_range, - l2_range, - l1_transition, - l2_transition, - tip_id, - (0, current_l1_state.total_accumulated_pow), - rollup_params_commitment, - ); + // Build the batch transition and batch info. + let new_transition = + BatchTransition::new(l1_transition, l2_transition, rollup_params_commitment); + let new_batch = BatchInfo::new(first_checkpoint_idx, l1_range, l2_range); + let genesis_state = new_transition.get_initial_base_state_commitment(); - let genesis_bootstrap = new_batch.get_initial_bootstrap_state(); - Ok((new_batch, genesis_bootstrap)) + Ok((new_batch, new_transition, genesis_state)) } - Some(prev_checkpoint) => { - let checkpoint = prev_checkpoint.batch_info.clone(); - let current_l1_state = state - .l1_view() - .tip_verification_state() - .ok_or(Error::ChainInactive)?; - let l1_range = ( - checkpoint.l1_range.1 + 1, - current_l1_state.last_verified_block_num as u64, - ); - let current_l1_state_hash = current_l1_state.compute_hash().unwrap(); - let l1_transition = (checkpoint.l1_transition.1, current_l1_state_hash); - - // Also, rather than tip heights, we might need to limit the max range a prover will - // be proving - let l2_range = (checkpoint.l2_range.1 + 1, tip_height); + // --- Branch: Subsequent batches (using the last finalized checkpoint) --- + Some(prev_checkpoint) => { + let batch_info = prev_checkpoint.batch_info.clone(); + let batch_transition = prev_checkpoint.batch_transition.clone(); + + // Build the L1 range for the new batch. + let initial_l1_height = batch_info.l1_range.1.height() + 1; + let initial_l1_commitment = get_l1_commitment(initial_l1_height)?; + let final_l1_height = current_l1_state.last_verified_block_num as u64; + // Use the block id from the current verification state. + let final_l1_commitment = + L1BlockCommitment::new(final_l1_height, current_l1_state.last_verified_block_hash); + let l1_range = (initial_l1_commitment, final_l1_commitment); + let l1_transition = (batch_transition.l1_transition.1, current_l1_state_hash); + + // Build the L2 range for the new batch. + let initial_l2_height = batch_info.l2_range.1.slot() + 1; + let initial_l2_commitment = get_l2_commitment(initial_l2_height)?; + let final_l2_commitment = L2BlockCommitment::new(tip_height, tip_id); + let l2_range = (initial_l2_commitment, final_l2_commitment); let current_chain_state = chsman .get_toplevel_chainstate_blocking(tip_height)? - .ok_or(Error::MissingIdxChainstate(0))?; + .ok_or(Error::MissingIdxChainstate(tip_height))?; let current_chain_state_root = current_chain_state.compute_state_root(); - let l2_transition = (checkpoint.l2_transition.1, current_chain_state_root); - - let new_batch = BatchInfo::new( - checkpoint.idx + 1, - l1_range, - l2_range, - l1_transition, - l2_transition, - tip_id, - ( - checkpoint.l1_pow_transition.1, - current_l1_state.total_accumulated_pow, - ), - rollup_params_commitment, - ); + let l2_transition = (batch_transition.l2_transition.1, current_chain_state_root); + + let new_batch_info = BatchInfo::new(batch_info.epoch + 1, l1_range, l2_range); + let new_transition = + BatchTransition::new(l1_transition, l2_transition, rollup_params_commitment); - // If prev checkpoint was proved, use the bootstrap state of the prev checkpoint - // else create a bootstrap state based on initial info of this batch - let bootstrap_state = if prev_checkpoint.is_proved { - prev_checkpoint.bootstrap_state.clone() + let base_state_commitment = if prev_checkpoint.is_proved { + prev_checkpoint.base_state_commitment.clone() } else { - new_batch.get_initial_bootstrap_state() + new_transition.get_initial_base_state_commitment() }; - Ok((new_batch, bootstrap_state)) + + Ok((new_batch_info, new_transition, base_state_commitment)) } } } diff --git a/crates/sequencer/src/duty/types.rs b/crates/sequencer/src/duty/types.rs index f35bbfade..47f781dc7 100644 --- a/crates/sequencer/src/duty/types.rs +++ b/crates/sequencer/src/duty/types.rs @@ -41,7 +41,7 @@ impl Duty { pub fn expiry(&self) -> Expiry { match self { Self::SignBlock(_) => Expiry::NextBlock, - Self::CommitBatch(duty) => Expiry::CheckpointIdxFinalized(duty.idx()), + Self::CommitBatch(duty) => Expiry::CheckpointIdxFinalized(duty.0.batch_info().epoch()), } } @@ -49,7 +49,7 @@ impl Duty { pub fn id(&self) -> Buf32 { match self { // We want Batch commitment duty to be unique by the checkpoint idx - Self::CommitBatch(duty) => compute_borsh_hash(&duty.idx()), + Self::CommitBatch(duty) => compute_borsh_hash(&duty.0.batch_info().epoch()), _ => compute_borsh_hash(self), } } @@ -96,24 +96,22 @@ impl BlockSigningDuty { /// When this duty is created, in order to execute the duty, the sequencer looks for corresponding /// batch proof in the proof db. #[derive(Clone, Debug, BorshSerialize, Serialize, Deserialize)] -pub struct BatchCheckpointDuty { - checkpoint: BatchCheckpoint, -} +pub struct BatchCheckpointDuty(BatchCheckpoint); impl BatchCheckpointDuty { - /// Create new duty from [`BatchCheckpoint`] - pub fn new(checkpoint: BatchCheckpoint) -> Self { - Self { checkpoint } + /// Creates a new `BatchCheckpointDuty` from a `BatchCheckpoint`. + pub fn new(batch_checkpoint: BatchCheckpoint) -> Self { + Self(batch_checkpoint) } - /// Gen checkpoint index. - pub fn idx(&self) -> u64 { - self.checkpoint.batch_info().idx() + /// Consumes `self`, returning the inner `BatchCheckpoint`. + pub fn into_inner(self) -> BatchCheckpoint { + self.0 } - /// Get reference to checkpoint. - pub fn checkpoint(&self) -> &BatchCheckpoint { - &self.checkpoint + /// Returns a reference to the inner `BatchCheckpoint`. + pub fn inner(&self) -> &BatchCheckpoint { + &self.0 } } diff --git a/crates/sequencer/src/duty/worker.rs b/crates/sequencer/src/duty/worker.rs index 20af81b03..482dc89ba 100644 --- a/crates/sequencer/src/duty/worker.rs +++ b/crates/sequencer/src/duty/worker.rs @@ -142,7 +142,7 @@ fn update_tracker( let latest_finalized_batch = state .l1_view() .last_finalized_checkpoint() - .map(|x| x.batch_info.idx()); + .map(|x| x.batch_info.epoch()); let tracker_update = StateUpdate::new( block_idx, diff --git a/crates/state/src/batch.rs b/crates/state/src/batch.rs index ec3e69612..a22603214 100644 --- a/crates/state/src/batch.rs +++ b/crates/state/src/batch.rs @@ -2,13 +2,17 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use strata_crypto::verify_schnorr_sig; -use strata_primitives::buf::{Buf32, Buf64}; +use strata_primitives::{ + buf::{Buf32, Buf64}, + l1::L1BlockCommitment, + l2::{L2BlockCommitment, L2BlockId}, +}; use zkaleido::{Proof, ProofReceipt, PublicValues}; -use crate::id::L2BlockId; - -/// Public parameters for batch proof to be posted to DA. -/// Will be updated as prover specs evolve. +/// Consolidates all information required to describe and verify a batch checkpoint. +/// This includes metadata about the batch, the state transitions, checkpoint base state, +/// and the proof itself. The proof verifies that the transition in [`BatchTransition`] +/// is valid for the batch described by [`BatchInfo`]. #[derive( Clone, Debug, PartialEq, Eq, Arbitrary, BorshDeserialize, BorshSerialize, Deserialize, Serialize, )] @@ -16,18 +20,28 @@ pub struct BatchCheckpoint { /// Information regarding the current batch checkpoint batch_info: BatchInfo, - /// Bootstrap info based on which the checkpoint transition and proof is verified - bootstrap: BootstrapState, + /// Transition data for L1 and L2 states, which is verified by the proof. + transition: BatchTransition, + + /// Reference state commitment against which batch transition and corresponding proof is + /// verified + base_state_commitment: BaseStateCommitment, /// Proof for the batch obtained from prover manager proof: Proof, } impl BatchCheckpoint { - pub fn new(batch_info: BatchInfo, bootstrap: BootstrapState, proof: Proof) -> Self { + pub fn new( + batch_info: BatchInfo, + transition: BatchTransition, + base_state_commitment: BaseStateCommitment, + proof: Proof, + ) -> Self { Self { batch_info, - bootstrap, + transition, + base_state_commitment, proof, } } @@ -36,27 +50,38 @@ impl BatchCheckpoint { &self.batch_info } - pub fn bootstrap_state(&self) -> &BootstrapState { - &self.bootstrap + pub fn batch_transition(&self) -> &BatchTransition { + &self.transition + } + + pub fn base_state_commitment(&self) -> &BaseStateCommitment { + &self.base_state_commitment + } + + pub fn proof(&self) -> &Proof { + &self.proof + } + + pub fn update_proof(&mut self, proof: Proof) { + self.proof = proof } - pub fn proof_output(&self) -> CheckpointProofOutput { - CheckpointProofOutput::new(self.batch_info().clone(), self.bootstrap_state().clone()) + pub fn get_proof_output(&self) -> CheckpointProofOutput { + CheckpointProofOutput::new( + self.batch_transition().clone(), + self.base_state_commitment().clone(), + ) } - pub fn into_proof_receipt(self) -> ProofReceipt { - let proof = self.proof; - let output = CheckpointProofOutput::new(self.batch_info, self.bootstrap); + pub fn get_proof_receipt(&self) -> ProofReceipt { + let proof = self.proof().clone(); + let output = self.get_proof_output(); let public_values = PublicValues::new( borsh::to_vec(&output).expect("could not serialize checkpoint proof output"), ); ProofReceipt::new(proof, public_values) } - pub fn proof(&self) -> &Proof { - &self.proof - } - pub fn hash(&self) -> Buf32 { let mut buf = vec![]; let batch_serialized = @@ -102,178 +127,155 @@ impl From for BatchCheckpoint { } } +/// Contains metadata describing a batch checkpoint, including the L1 and L2 height ranges +/// it covers and the final L2 block ID in that range. #[derive( Clone, Debug, Eq, PartialEq, Arbitrary, BorshDeserialize, BorshSerialize, Deserialize, Serialize, )] pub struct BatchInfo { - /// The index of the checkpoint - pub idx: u64, - - /// L1 height range(inclusive) the checkpoint covers - pub l1_range: (u64, u64), + /// Checkpoint epoch + pub epoch: u64, - /// L2 height range(inclusive) the checkpoint covers - pub l2_range: (u64, u64), + /// L1 block range(inclusive) the checkpoint covers + pub l1_range: (L1BlockCommitment, L1BlockCommitment), - /// The inclusive hash range of `HeaderVerificationState` for L1 blocks. - /// This represents the transition of L1 state from the starting state to the - /// ending state. The hash is computed via - /// [`super::l1::HeaderVerificationState::compute_hash`]. - pub l1_transition: (Buf32, Buf32), - - /// The inclusive hash range of `Chainstate` for L2 blocks. - /// This represents the transition of L2 state from the starting state to the - /// ending state. The state root is computed via - /// [`super::chain_state::Chainstate::compute_state_root`]. - pub l2_transition: (Buf32, Buf32), - - /// The last L2 block upto which this checkpoint covers since the previous checkpoint - pub l2_blockid: L2BlockId, - - /// PoW transition in the given `l1_range` - pub l1_pow_transition: (u128, u128), - - /// Commitment of the `RollupParams` calculated by - /// [`strata_primitives::params::RollupParams::compute_hash`] - pub rollup_params_commitment: Buf32, + /// L2 block range(inclusive) the checkpoint covers + pub l2_range: (L2BlockCommitment, L2BlockCommitment), } impl BatchInfo { - #[allow(clippy::too_many_arguments)] // FIXME pub fn new( checkpoint_idx: u64, - l1_range: (u64, u64), - l2_range: (u64, u64), - l1_transition: (Buf32, Buf32), - l2_transition: (Buf32, Buf32), - l2_blockid: L2BlockId, - l1_pow_transition: (u128, u128), - rollup_params_commitment: Buf32, + l1_range: (L1BlockCommitment, L1BlockCommitment), + l2_range: (L2BlockCommitment, L2BlockCommitment), ) -> Self { Self { - idx: checkpoint_idx, + epoch: checkpoint_idx, l1_range, l2_range, - l1_transition, - l2_transition, - l2_blockid, - l1_pow_transition, - rollup_params_commitment, } } - pub fn idx(&self) -> u64 { - self.idx + pub fn epoch(&self) -> u64 { + self.epoch } - pub fn l2_blockid(&self) -> &L2BlockId { - &self.l2_blockid + /// Returns the final L2 block commitment in the batch's L2 range. + pub fn final_l2_blockid(&self) -> &L2BlockId { + self.l2_range.1.blkid() } - pub fn initial_l1_state_hash(&self) -> &Buf32 { - &self.l1_transition.0 + /// check for whether the l2 block is covered by the checkpoint + pub fn includes_l2_block(&self, l2_block_height: u64) -> bool { + let (_, last_l2_commitment) = self.l2_range; + if l2_block_height <= last_l2_commitment.slot() { + return true; + } + false } - pub fn final_l1_state_hash(&self) -> &Buf32 { - &self.l1_transition.1 + /// check for whether the l1 block is covered by the checkpoint + pub fn includes_l1_block(&self, l1_block_height: u64) -> bool { + let (_, last_l1_commitment) = self.l1_range; + if l1_block_height <= last_l1_commitment.height() { + return true; + } + false } +} - pub fn initial_l2_state_hash(&self) -> &Buf32 { - &self.l2_transition.0 - } +/// Describes state transitions for both L1 and L2, along with a commitment to the +/// rollup parameters. The proof associated with the batch verifies this transition. +#[derive( + Clone, Debug, Eq, PartialEq, Arbitrary, BorshDeserialize, BorshSerialize, Deserialize, Serialize, +)] +pub struct BatchTransition { + /// The inclusive hash range of `HeaderVerificationState` for L1 blocks. + /// + /// Represents a transition from the starting L1 state to the ending L1 state. + /// The hash is computed via [`super::l1::HeaderVerificationState::compute_hash`]. + pub l1_transition: (Buf32, Buf32), - pub fn final_l2_state_hash(&self) -> &Buf32 { - &self.l2_transition.1 - } + /// The inclusive hash range of `Chainstate` for L2 blocks. + /// + /// Represents a transition from the starting L2 state to the ending L2 state. + /// The state root is computed via [`super::chain_state::Chainstate::compute_state_root`]. + pub l2_transition: (Buf32, Buf32), - pub fn initial_acc_pow(&self) -> u128 { - self.l1_pow_transition.0 - } + /// A commitment to the `RollupParams`, as computed by + /// [`strata_primitives::params::RollupParams::compute_hash`]. + /// + /// This indicates that the transition is valid under these rollup parameters. + pub rollup_params_commitment: Buf32, +} - pub fn final_acc_pow(&self) -> u128 { - self.l1_pow_transition.1 +impl BatchTransition { + pub fn new( + l1_transition: (Buf32, Buf32), + l2_transition: (Buf32, Buf32), + rollup_params_commitment: Buf32, + ) -> Self { + Self { + l1_transition, + l2_transition, + rollup_params_commitment, + } } - pub fn rollup_params_commitment(&self) -> Buf32 { - self.rollup_params_commitment + /// Creates a [`BaseStateCommitment`] by taking the initial state of the + /// [`BatchTransition`] + pub fn get_initial_base_state_commitment(&self) -> BaseStateCommitment { + BaseStateCommitment::new(self.l1_transition.0, self.l2_transition.0) } - /// Creates a [`BootstrapState`] by taking the initial state of the [`BatchInfo`] - pub fn get_initial_bootstrap_state(&self) -> BootstrapState { - BootstrapState::new( - self.l1_range.0, - self.l1_transition.0, - self.l2_range.0, - self.l2_transition.0, - self.l1_pow_transition.0, - ) + /// Creates a [`BaseStateCommitment`] by taking the final state of the + /// [`BatchTransition`] + pub fn get_final_base_state_commitment(&self) -> BaseStateCommitment { + BaseStateCommitment::new(self.l1_transition.1, self.l2_transition.1) } - /// Creates a [`BootstrapState`] by taking the final state of the [`BatchInfo`] - pub fn get_final_bootstrap_state(&self) -> BootstrapState { - BootstrapState::new( - self.l1_range.1 + 1, // because each batch is inclusive - self.l1_transition.1, - self.l2_range.1 + 1, // because each batch is inclusive - self.l2_transition.1, - self.l1_pow_transition.1, - ) - } - /// check for whether the l2 block is covered by the checkpoint - pub fn includes_l2_block(&self, block_height: u64) -> bool { - let (_, last_l2_height) = self.l2_range; - if block_height <= last_l2_height { - return true; - } - false + pub fn rollup_params_commitment(&self) -> Buf32 { + self.rollup_params_commitment } } -/// Initial state to bootstrap the proving process +/// Represents the reference state commitment against which batch transitions and proofs are +/// verified. /// -/// TODO: This needs to be replaced with GenesisState if we prove each Checkpoint -/// recursively. Using a BootstrapState is a temporary solution +/// NOTE/TODO: This state serves as the starting point for verifying a checkpoint proof. If we move +/// towards a strict mode where we prove each checkpoint recursively, this should be replaced with +/// `GenesisState`. #[derive( Clone, Debug, PartialEq, Eq, Arbitrary, BorshDeserialize, BorshSerialize, Deserialize, Serialize, )] -pub struct BootstrapState { - pub start_l1_height: u64, - // TODO is this a blkid? +pub struct BaseStateCommitment { pub initial_l1_state: Buf32, - pub start_l2_height: u64, pub initial_l2_state: Buf32, - pub total_acc_pow: u128, } -impl BootstrapState { - pub fn new( - start_l1_height: u64, - initial_l1_state: Buf32, - start_l2_height: u64, - initial_l2_state: Buf32, - total_acc_pow: u128, - ) -> Self { +impl BaseStateCommitment { + pub fn new(initial_l1_state: Buf32, initial_l2_state: Buf32) -> Self { Self { - start_l1_height, initial_l1_state, - start_l2_height, initial_l2_state, - total_acc_pow, } } } #[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize)] pub struct CheckpointProofOutput { - pub batch_info: BatchInfo, - pub bootstrap_state: BootstrapState, + pub batch_transition: BatchTransition, + pub base_state_commitment: BaseStateCommitment, } impl CheckpointProofOutput { - pub fn new(batch_info: BatchInfo, bootstrap_state: BootstrapState) -> CheckpointProofOutput { + pub fn new( + batch_transition: BatchTransition, + base_state_commitment: BaseStateCommitment, + ) -> CheckpointProofOutput { Self { - batch_info, - bootstrap_state, + batch_transition, + base_state_commitment, } } } diff --git a/crates/state/src/client_state.rs b/crates/state/src/client_state.rs index 215d5f7c6..61984ed3a 100644 --- a/crates/state/src/client_state.rs +++ b/crates/state/src/client_state.rs @@ -10,7 +10,7 @@ use strata_primitives::buf::Buf32; use tracing::*; use crate::{ - batch::{BatchInfo, BootstrapState}, + batch::{BaseStateCommitment, BatchInfo, BatchTransition}, id::L2BlockId, l1::{HeaderVerificationState, L1BlockId}, operation::{ClientUpdateOutput, SyncAction}, @@ -303,9 +303,11 @@ pub struct L1Checkpoint { /// The inner checkpoint batch info pub batch_info: BatchInfo, - /// The inner checkpoint batch info - // TODO why is this called bootstrap state? - pub bootstrap_state: BootstrapState, + /// The inner checkpoint batch transition + pub batch_transition: BatchTransition, + + /// Reference state commitment against which batch transitions is verified + pub base_state_commitment: BaseStateCommitment, /// If the checkpoint included proof pub is_proved: bool, @@ -317,13 +319,15 @@ pub struct L1Checkpoint { impl L1Checkpoint { pub fn new( batch_info: BatchInfo, - bootstrap_state: BootstrapState, + batch_transition: BatchTransition, + base_state_commitment: BaseStateCommitment, is_proved: bool, height: u64, ) -> Self { Self { batch_info, - bootstrap_state, + batch_transition, + base_state_commitment, is_proved, height, } @@ -496,12 +500,14 @@ impl ClientStateMut { if l1v .last_finalized_checkpoint .as_ref() - .is_some_and(|prev_ckpt| ckpt.batch_info.idx() != prev_ckpt.batch_info.idx() + 1) + .is_some_and(|prev_ckpt| { + ckpt.batch_info.epoch() != prev_ckpt.batch_info.epoch() + 1 + }) { panic!("clientstate: mismatched indices of pending checkpoint"); } - let fin_blockid = *ckpt.batch_info.l2_blockid(); + let fin_blockid = *ckpt.batch_info.final_l2_blockid(); l1v.last_finalized_checkpoint = Some(ckpt); // Update finalized blockid in StateSync diff --git a/crates/state/src/l1/header_verification.rs b/crates/state/src/l1/header_verification.rs index 022750778..25e276846 100644 --- a/crates/state/src/l1/header_verification.rs +++ b/crates/state/src/l1/header_verification.rs @@ -70,22 +70,6 @@ pub struct HeaderVerificationState { pub last_11_blocks_timestamps: TimestampStore, } -/// Summary of the HeaderVerificationState that is propagated to the CheckpointProof as public -/// output -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Deserialize, Serialize)] -pub struct HeaderVerificationStateSnapshot { - /// Hash of the [`HeaderVerificationState`] - pub hash: Buf32, - - /// [HeaderVerificationState::last_verified_block_num] - /// - /// Note: This field and struct is here only since `BatchInfo` requires that - pub block_num: u64, - - /// Total accumulated [difficulty](bitcoin::pow::Target::difficulty) - pub acc_pow: u128, -} - impl HeaderVerificationState { /// Computes the [`CompactTarget`] from a difficulty adjustment. /// @@ -237,23 +221,6 @@ impl HeaderVerificationState { new_self } - // Need to improve upon this? - pub fn compute_initial_snapshot(&self) -> HeaderVerificationStateSnapshot { - HeaderVerificationStateSnapshot { - hash: self.compute_hash().unwrap(), - block_num: self.last_verified_block_num as u64 + 1, // because inclusive - acc_pow: self.total_accumulated_pow, - } - } - - pub fn compute_final_snapshot(&self) -> HeaderVerificationStateSnapshot { - HeaderVerificationStateSnapshot { - hash: self.compute_hash().unwrap(), - block_num: self.last_verified_block_num as u64, - acc_pow: self.total_accumulated_pow, - } - } - /// Calculate the hash of the verification state pub fn compute_hash(&self) -> Result { // 4 + 32 + 4 + 4 + 16 + 11*4 = 104 diff --git a/crates/state/src/operation.rs b/crates/state/src/operation.rs index dab8c8465..29d506429 100644 --- a/crates/state/src/operation.rs +++ b/crates/state/src/operation.rs @@ -245,13 +245,13 @@ pub fn apply_writes_to_state( .last_finalized_checkpoint .as_ref() .is_none_or(|prev_chp| { - checkpt.batch_info.idx() == prev_chp.batch_info.idx() + 1 + checkpt.batch_info.epoch() == prev_chp.batch_info.epoch() + 1 }) { panic!("operation: mismatched indices of pending checkpoint"); } - let fin_blockid = *checkpt.batch_info.l2_blockid(); + let fin_blockid = *checkpt.batch_info.final_l2_blockid(); l1v.last_finalized_checkpoint = Some(checkpt); // Update finalized blockid in StateSync diff --git a/functional-tests/tests/sync/sync_bitcoin_reorg.py b/functional-tests/tests/sync/sync_bitcoin_reorg.py index a21c20d87..b08925f75 100644 --- a/functional-tests/tests/sync/sync_bitcoin_reorg.py +++ b/functional-tests/tests/sync/sync_bitcoin_reorg.py @@ -122,7 +122,7 @@ def check_nth_checkpoint_finalized_on_reorg( btcrpc.proxy.generatetoaddress(finality_depth + 1, new_addr) batch_info = seqrpc.strata_getCheckpointInfo(checkpt_idx) - to_finalize_blkid = batch_info["l2_blockid"] + to_finalize_blkid = batch_info["l2_range"][1]["blkid"] # Check finalized wait_until( diff --git a/functional-tests/utils/utils.py b/functional-tests/utils/utils.py index ae051e723..9d5a04e07 100644 --- a/functional-tests/utils/utils.py +++ b/functional-tests/utils/utils.py @@ -172,14 +172,14 @@ def check_nth_checkpoint_finalized( timeout=3, ) - assert syncstat["finalized_block_id"] != batch_info["l2_blockid"], ( + assert syncstat["finalized_block_id"] != batch_info["l2_range"][1]["blkid"], ( "Checkpoint block should not yet finalize" ) assert batch_info["idx"] == idx checkpoint_info_next = seqrpc.strata_getCheckpointInfo(idx + 1) assert checkpoint_info_next is None, f"There should be no checkpoint info for {idx + 1} index" - to_finalize_blkid = batch_info["l2_blockid"] + to_finalize_blkid = batch_info["l2_range"][1]["blkid"] # Submit checkpoint if proof_timeout is not set if proof_timeout is None: