diff --git a/Cargo.lock b/Cargo.lock index 246290ba9..ebfeb63ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1900,6 +1900,7 @@ dependencies = [ "alloy-primitives", "anyhow", "backoff", + "borsh", "citrea-primitives", "citrea-storage-ops", "futures", @@ -1908,6 +1909,8 @@ dependencies = [ "jsonrpsee", "lru", "metrics", + "rocksdb", + "rs_merkle", "serde", "serde_json", "sov-db", @@ -8010,6 +8013,7 @@ version = "0.6.0" dependencies = [ "anyhow", "borsh", + "citrea-primitives", "hex", "itertools 0.13.0", "jmt", diff --git a/bin/citrea/src/rollup/mod.rs b/bin/citrea/src/rollup/mod.rs index 68335062b..d0216d704 100644 --- a/bin/citrea/src/rollup/mod.rs +++ b/bin/citrea/src/rollup/mod.rs @@ -413,7 +413,7 @@ pub trait CitreaRollupBlueprint: RollupBlueprint { let genesis_root = prover_storage.get_root_hash(1); if let Ok(state_root) = genesis_root { - // Chain was initialized but no soft confirmations was processed + // Chain was initialized but no L2 blocks were processed debug!("Chain is already initialized. Skipping initialization."); return Ok(InitParams { state_root, diff --git a/bin/citrea/src/test_rpc.rs b/bin/citrea/src/test_rpc.rs index f1e1ccf5a..9d4a19089 100644 --- a/bin/citrea/src/test_rpc.rs +++ b/bin/citrea/src/test_rpc.rs @@ -1,3 +1,4 @@ +use citrea_common::utils::compute_tx_merkle_root; #[cfg(test)] use citrea_common::RpcConfig; use hex::ToHex; @@ -6,10 +7,10 @@ use sha2::Digest; use sov_db::ledger_db::{LedgerDB, SharedLedgerOps}; use sov_db::rocks_db_config::RocksdbConfig; use sov_db::schema::types::soft_confirmation::StoredTransaction; -use sov_mock_da::MockDaSpec; -#[cfg(test)] -use sov_modules_api::DaSpec; -use sov_rollup_interface::stf::SoftConfirmationReceipt; +use sov_modules_api::L2Block; +use sov_rollup_interface::soft_confirmation::{ + SignedSoftConfirmationHeader, SoftConfirmationHeader, +}; struct TestExpect { payload: serde_json::Value, @@ -40,26 +41,21 @@ async fn queries_test_runner(test_queries: Vec, rpc_config: RpcConfi } } -fn populate_ledger( - ledger_db: &mut LedgerDB, - state_root: &[u8], - soft_confirmation_receipts: Vec>, - tx_bodies: Vec>>, -) { - for (soft_confirmation_receipt, tx_bodies) in - soft_confirmation_receipts.into_iter().zip(tx_bodies) - { +fn populate_ledger(ledger_db: &mut LedgerDB, l2_blocks: Vec>) { + for block in l2_blocks { + let tx_hashes = block.txs.to_vec(); + let tx_bodies = block + .txs + .iter() + .map(|tx| borsh::to_vec(tx).expect("Tx serialization shouldn't fail")) + .collect(); ledger_db - .commit_soft_confirmation(state_root, soft_confirmation_receipt, Some(tx_bodies)) + .commit_l2_block(block.clone(), tx_hashes, Some(tx_bodies)) .unwrap(); } } -fn test_helper( - test_queries: Vec, - soft_confirmation_receipts: Vec>, - tx_bodies: Vec>>, -) { +fn test_helper(test_queries: Vec, l2_blocks: Vec>) { let rt = tokio::runtime::Builder::new_multi_thread() .enable_io() .enable_time() @@ -71,12 +67,7 @@ fn test_helper( let tmpdir = tempfile::tempdir().unwrap(); let mut ledger_db = LedgerDB::with_config(&RocksdbConfig::new(tmpdir.path(), None, None)).unwrap(); - populate_ledger( - &mut ledger_db, - &[1; 32], - soft_confirmation_receipts, - tx_bodies, - ); + populate_ledger(&mut ledger_db, l2_blocks); let server = jsonrpsee::server::ServerBuilder::default() .build("127.0.0.1:0") .await @@ -112,54 +103,75 @@ fn batch2_tx_receipts() -> (Vec, Vec>) { } fn regular_test_helper(payload: serde_json::Value, expected: &serde_json::Value) { - let tx_bodies_1 = vec![b"tx1 body".to_vec(), b"tx2 body".to_vec()]; - let (batch_2_receipts, tx_bodies_2) = batch2_tx_receipts(); - let soft_confirmation_receipts = vec![ - SoftConfirmationReceipt { - l2_height: 1, - da_slot_height: 0, - da_slot_hash: ::SlotHash::from([0u8; 32]), - da_slot_txs_commitment: ::SlotHash::from([1u8; 32]), - soft_confirmation_signature: vec![], - hash: ::sha2::Sha256::digest(b"batch_receipt").into(), - prev_hash: ::sha2::Sha256::digest(b"prev_batch_receipt").into(), - tx_hashes: vec![ - ::sha2::Sha256::digest(b"tx1").into(), - ::sha2::Sha256::digest(b"tx2").into(), - ], - pub_key: vec![], - deposit_data: vec![ - "aaaaab".as_bytes().to_vec(), - "eeeeeeeeee".as_bytes().to_vec(), - ], - l1_fee_rate: 0, - timestamp: 0, - }, - SoftConfirmationReceipt { - l2_height: 2, - da_slot_height: 1, - da_slot_hash: ::SlotHash::from([2; 32]), - da_slot_txs_commitment: ::SlotHash::from([3; 32]), - soft_confirmation_signature: vec![], - hash: ::sha2::Sha256::digest(b"batch_receipt2").into(), - prev_hash: ::sha2::Sha256::digest(b"prev_batch_receipt2").into(), - tx_hashes: batch_2_receipts.iter().map(|r| r.hash).collect(), - pub_key: vec![], - deposit_data: vec!["c44444".as_bytes().to_vec()], - l1_fee_rate: 0, - timestamp: 0, - }, + let (batch_2_receipts, _) = batch2_tx_receipts(); + + let tx_hashes_1 = vec![ + ::sha2::Sha256::digest(b"tx1").into(), + ::sha2::Sha256::digest(b"tx2").into(), ]; - let tx_bodies = vec![tx_bodies_1, tx_bodies_2]; + let header1 = SoftConfirmationHeader::new( + 1, + 0, + [0u8; 32], + [1u8; 32], + ::sha2::Sha256::digest(b"prev_batch_receipt").into(), + [1; 32], + 0, + compute_tx_merkle_root(&tx_hashes_1).unwrap(), + vec![ + "aaaaab".as_bytes().to_vec(), + "eeeeeeeeee".as_bytes().to_vec(), + ], + 0, + ); + + let header2 = SoftConfirmationHeader::new( + 2, + 1, + [2; 32], + [3; 32], + ::sha2::Sha256::digest(b"prev_batch_receipt2").into(), + [1; 32], + 0, + compute_tx_merkle_root(&batch_2_receipts.iter().map(|r| r.hash).collect::>()) + .unwrap(), + vec!["c44444".as_bytes().to_vec()], + 0, + ); + + let signed_header1 = SignedSoftConfirmationHeader::new( + header1, + ::sha2::Sha256::digest(b"batch_receipt").into(), + vec![], + vec![], + ); + + let signed_header2 = SignedSoftConfirmationHeader::new( + header2, + ::sha2::Sha256::digest(b"batch_receipt2").into(), + vec![], + vec![], + ); + + let l2_blocks = vec![ + L2Block::<[u8; 32]>::new(signed_header1, tx_hashes_1.into()), + L2Block::<[u8; 32]>::new( + signed_header2, + batch_2_receipts + .iter() + .map(|r| r.hash) + .collect::>() + .into(), + ), + ]; test_helper( vec![TestExpect { payload, expected: expected.clone(), }], - soft_confirmation_receipts, - tx_bodies, + l2_blocks, ) } @@ -196,7 +208,13 @@ macro_rules! jsonrpc_result { fn test_get_soft_confirmation() { // Get the first soft confirmation by number let payload = jsonrpc_req!("ledger_getSoftConfirmationByNumber", [1]); - let expected = jsonrpc_result!({"daSlotHeight":0,"daSlotHash":"0000000000000000000000000000000000000000000000000000000000000000","daSlotTxsCommitment":"0101010101010101010101010101010101010101010101010101010101010101","depositData": ["616161616162", "65656565656565656565"],"hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","l2Height":1, "txs":["74783120626f6479", "74783220626f6479"],"prevHash":"0209d4aa08c40ed0fcb2bb6eb276481f2ad045914c3065e13e4f1657e97638b1","stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"", "l1FeeRate":0, "timestamp": 0}); + + let tx_hashes = vec![ + ::sha2::Sha256::digest(b"tx1").into(), + ::sha2::Sha256::digest(b"tx2").into(), + ]; + let empty_tx_merkle_root = compute_tx_merkle_root(&tx_hashes).unwrap(); + let expected = jsonrpc_result!({"daSlotHeight":0,"daSlotHash":"0000000000000000000000000000000000000000000000000000000000000000","daSlotTxsCommitment":"0101010101010101010101010101010101010101010101010101010101010101","depositData": ["616161616162", "65656565656565656565"],"hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","l2Height":1, "txs":["709b55bd3da0f5a838125bd0ee20c5bfdd7caba173912d4281cae816b79a201b", "27ca64c092a959c7edc525ed45e845b1de6a7590d173fd2fad9133c8a779a1e3"],"prevHash":"0209d4aa08c40ed0fcb2bb6eb276481f2ad045914c3065e13e4f1657e97638b1","stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"", "l1FeeRate":0, "timestamp": 0, "txMerkleRoot": empty_tx_merkle_root}); regular_test_helper(payload, &expected); // Get the first soft confirmation by hash @@ -209,12 +227,19 @@ fn test_get_soft_confirmation() { // Get the second soft confirmation by number let payload = jsonrpc_req!("ledger_getSoftConfirmationByNumber", [2]); let txs = batch2_tx_receipts() - .1 + .0 .into_iter() - .map(|body| body.encode_hex::()) + .map(|r| borsh::to_vec(&r.hash).unwrap().encode_hex::()) .collect::>(); + + let tx_hashes = batch2_tx_receipts() + .0 + .iter() + .map(|r| r.hash) + .collect::>(); + let tx_merkle_root = compute_tx_merkle_root(&tx_hashes).unwrap(); let expected = jsonrpc_result!( - {"daSlotHeight":1,"daSlotHash":"0202020202020202020202020202020202020202020202020202020202020202","daSlotTxsCommitment":"0303030303030303030303030303030303030303030303030303030303030303","depositData": ["633434343434"],"hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","l2Height":2, "txs": txs, "prevHash":"11ec8b9896aa1f400cc1dbd1b0ab3dcc97f2025b3d309b70ec249f687a807d1d","stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"","l1FeeRate":0, "timestamp": 0} + {"daSlotHeight":1,"daSlotHash":"0202020202020202020202020202020202020202020202020202020202020202","daSlotTxsCommitment":"0303030303030303030303030303030303030303030303030303030303030303","depositData": ["633434343434"],"hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","l2Height":2, "txs": txs, "prevHash":"11ec8b9896aa1f400cc1dbd1b0ab3dcc97f2025b3d309b70ec249f687a807d1d","stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"","l1FeeRate":0, "timestamp": 0, "txMerkleRoot": tx_merkle_root} ); regular_test_helper(payload, &expected); @@ -229,14 +254,21 @@ fn test_get_soft_confirmation() { let payload = jsonrpc_req!("ledger_getSoftConfirmationRange", [1, 2]); let txs = batch2_tx_receipts() - .1 + .0 .into_iter() - .map(|body| body.encode_hex::()) + .map(|r| borsh::to_vec(&r.hash).unwrap().encode_hex::()) .collect::>(); + + let tx_hashes = batch2_tx_receipts() + .0 + .iter() + .map(|r| r.hash) + .collect::>(); + let tx_merkle_root = compute_tx_merkle_root(&tx_hashes).unwrap(); let expected = jsonrpc_result!( [ - {"daSlotHeight":0,"daSlotHash":"0000000000000000000000000000000000000000000000000000000000000000","daSlotTxsCommitment":"0101010101010101010101010101010101010101010101010101010101010101","depositData": ["616161616162", "65656565656565656565"],"hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","l2Height":1,"txs":["74783120626f6479", "74783220626f6479"],"prevHash":"0209d4aa08c40ed0fcb2bb6eb276481f2ad045914c3065e13e4f1657e97638b1", "stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"","l1FeeRate":0, "timestamp": 0}, - {"daSlotHeight":1,"daSlotHash":"0202020202020202020202020202020202020202020202020202020202020202","daSlotTxsCommitment":"0303030303030303030303030303030303030303030303030303030303030303","depositData": ["633434343434"],"hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","l2Height":2,"txs": txs, "prevHash": "11ec8b9896aa1f400cc1dbd1b0ab3dcc97f2025b3d309b70ec249f687a807d1d", "stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"","l1FeeRate":0, "timestamp": 0} + {"daSlotHeight":0,"daSlotHash":"0000000000000000000000000000000000000000000000000000000000000000","daSlotTxsCommitment":"0101010101010101010101010101010101010101010101010101010101010101","depositData": ["616161616162", "65656565656565656565"],"hash":"b5515a80204963f7db40e98af11aedb49a394b1c7e3d8b5b7a33346b8627444f","l2Height":1,"txs":["709b55bd3da0f5a838125bd0ee20c5bfdd7caba173912d4281cae816b79a201b", "27ca64c092a959c7edc525ed45e845b1de6a7590d173fd2fad9133c8a779a1e3"],"prevHash":"0209d4aa08c40ed0fcb2bb6eb276481f2ad045914c3065e13e4f1657e97638b1", "stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"","l1FeeRate":0, "timestamp": 0, "txMerkleRoot": empty_tx_merkle_root}, + {"daSlotHeight":1,"daSlotHash":"0202020202020202020202020202020202020202020202020202020202020202","daSlotTxsCommitment":"0303030303030303030303030303030303030303030303030303030303030303","depositData": ["633434343434"],"hash":"f85fe0cb36fdaeca571c896ed476b49bb3c8eff00d935293a8967e1e9a62071e","l2Height":2,"txs": txs, "prevHash": "11ec8b9896aa1f400cc1dbd1b0ab3dcc97f2025b3d309b70ec249f687a807d1d", "stateRoot":"0101010101010101010101010101010101010101010101010101010101010101","softConfirmationSignature":"","pubKey":"","l1FeeRate":0, "timestamp": 0, "txMerkleRoot": tx_merkle_root} ] ); regular_test_helper(payload, &expected); diff --git a/crates/batch-prover/src/da_block_handler.rs b/crates/batch-prover/src/da_block_handler.rs index 2099b5b3f..cc5a51009 100644 --- a/crates/batch-prover/src/da_block_handler.rs +++ b/crates/batch-prover/src/da_block_handler.rs @@ -21,7 +21,7 @@ use sov_db::schema::types::{SlotNumber, SoftConfirmationNumber}; use sov_modules_api::{DaSpec, StateDiff, Zkvm}; use sov_rollup_interface::da::{BlockHeaderTrait, SequencerCommitment}; use sov_rollup_interface::services::da::{DaService, SlotData}; -use sov_rollup_interface::soft_confirmation::SignedSoftConfirmation; +use sov_rollup_interface::soft_confirmation::L2Block; use sov_rollup_interface::spec::SpecId; use sov_rollup_interface::zk::ZkvmHost; use tokio::select; @@ -36,7 +36,7 @@ use crate::proving::{data_to_prove, extract_and_store_proof, prove_l1, GroupComm type CommitmentStateTransitionData<'txs, Witness, Da, Tx> = ( VecDeque>, - VecDeque>>, + VecDeque>>, VecDeque::Spec as DaSpec>::BlockHeader>>, ); @@ -300,7 +300,7 @@ pub(crate) async fn get_batch_proof_circuit_input_from_commitments< Da: DaService, DB: BatchProverLedgerOps, Witness: DeserializeOwned, - Tx: Clone + BorshDeserialize + 'txs, + Tx: Clone + BorshDeserialize + 'txs + BorshSerialize, >( sequencer_commitments: &[SequencerCommitment], da_service: &Arc, @@ -309,9 +309,9 @@ pub(crate) async fn get_batch_proof_circuit_input_from_commitments< ) -> Result, anyhow::Error> { let mut state_transition_witnesses: VecDeque> = VecDeque::with_capacity(sequencer_commitments.len()); - let mut soft_confirmations: VecDeque>> = + let mut committed_l2_blocks: VecDeque>> = VecDeque::with_capacity(sequencer_commitments.len()); - let mut da_block_headers_of_soft_confirmations: VecDeque< + let mut da_block_headers_of_l2_blocks: VecDeque< Vec<<::Spec as DaSpec>::BlockHeader>, > = VecDeque::with_capacity(sequencer_commitments.len()); for sequencer_commitment in sequencer_commitments.iter() { @@ -333,8 +333,7 @@ pub(crate) async fn get_batch_proof_circuit_input_from_commitments< )); } }; - let mut commitment_soft_confirmations = - Vec::with_capacity(soft_confirmations_in_commitment.len()); + let mut l2_blocks = Vec::with_capacity(soft_confirmations_in_commitment.len()); let mut da_block_headers_to_push: Vec<<::Spec as DaSpec>::BlockHeader> = vec![]; for soft_confirmation in soft_confirmations_in_commitment { @@ -359,14 +358,14 @@ pub(crate) async fn get_batch_proof_circuit_input_from_commitments< }; da_block_headers_to_push.push(filtered_block.header().clone()); } - let signed_soft_confirmation: SignedSoftConfirmation = soft_confirmation + let l2_block: L2Block = soft_confirmation .try_into() .context("Failed to parse transactions")?; - commitment_soft_confirmations.push(signed_soft_confirmation); + l2_blocks.push(l2_block); } - soft_confirmations.push_back(commitment_soft_confirmations); + committed_l2_blocks.push_back(l2_blocks); - da_block_headers_of_soft_confirmations.push_back(da_block_headers_to_push); + da_block_headers_of_l2_blocks.push_back(da_block_headers_to_push); for l2_height in sequencer_commitment.l2_start_block_number..=sequencer_commitment.l2_end_block_number { @@ -383,8 +382,8 @@ pub(crate) async fn get_batch_proof_circuit_input_from_commitments< } Ok(( state_transition_witnesses, - soft_confirmations, - da_block_headers_of_soft_confirmations, + committed_l2_blocks, + da_block_headers_of_l2_blocks, )) } diff --git a/crates/batch-prover/src/db_migrations/mod.rs b/crates/batch-prover/src/db_migrations/mod.rs index 65c52edb1..febb3032e 100644 --- a/crates/batch-prover/src/db_migrations/mod.rs +++ b/crates/batch-prover/src/db_migrations/mod.rs @@ -1,7 +1,8 @@ use std::sync::OnceLock; use citrea_common::db_migrations::{ - MigrateBatchAndSlotByNumber, MigrateVerifiedProofsBySlotNumber, RemoveUnusedTables, + MigrateBatchAndSlotByNumber, MigrateSoftConfirmationTxMerkleRoot, + MigrateVerifiedProofsBySlotNumber, RemoveUnusedTables, }; use sov_db::ledger_db::migrations::LedgerMigration; use sov_db::schema::tables::BATCH_PROVER_LEDGER_TABLES; @@ -16,6 +17,7 @@ pub fn migrations() -> &'static Vec( where Da: DaService, Witness: Default + BorshDeserialize + Serialize + DeserializeOwned, - Tx: Clone, + Tx: Clone + BorshSerialize, { for proof in proofs { let (initial_state_root, sequencer_commitments_range) = match &proof.proof_output { @@ -406,10 +403,7 @@ pub(crate) fn save_commitments( let l2_end_height = sequencer_commitment.l2_end_block_number; for i in l2_start_height..=l2_end_height { ledger_db - .put_soft_confirmation_status( - SoftConfirmationNumber(i), - SoftConfirmationStatus::Proven, - ) + .put_l2_block_status(SoftConfirmationNumber(i), SoftConfirmationStatus::Proven) .unwrap_or_else(|_| { panic!( "Failed to put soft confirmation status in the ledger db {}", diff --git a/crates/batch-prover/src/runner.rs b/crates/batch-prover/src/runner.rs index 8c2aeb2af..ef6226ab3 100644 --- a/crates/batch-prover/src/runner.rs +++ b/crates/batch-prover/src/runner.rs @@ -9,7 +9,7 @@ use backoff::exponential::ExponentialBackoffBuilder; use backoff::future::retry as retry_backoff; use citrea_common::cache::L1BlockCache; use citrea_common::da::get_da_block_at_height; -use citrea_common::utils::soft_confirmation_to_receipt; +use citrea_common::utils::compute_tx_hashes; use citrea_common::{InitParams, RollupPublicKeys, RunnerConfig}; use citrea_primitives::types::SoftConfirmationHash; use jsonrpsee::core::client::Error as JsonrpseeError; @@ -17,7 +17,7 @@ use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; use sov_db::ledger_db::BatchProverLedgerOps; use sov_db::schema::types::{SlotNumber, SoftConfirmationNumber}; use sov_ledger_rpc::LedgerRpcClient; -use sov_modules_api::{Context, SignedSoftConfirmation, SlotData, Spec}; +use sov_modules_api::{Context, L2Block, SlotData, Spec}; use sov_modules_stf_blueprint::{Runtime, StfBlueprint}; use sov_prover_storage_manager::{ProverStorage, ProverStorageManager, SnapshotManager}; use sov_rollup_interface::da::BlockHeaderTrait; @@ -208,11 +208,15 @@ where .storage_manager .create_storage_on_l2_height(l2_height)?; - let mut signed_soft_confirmation: SignedSoftConfirmation> = - soft_confirmation - .clone() - .try_into() - .context("Failed to parse transactions")?; + let tx_bodies = soft_confirmation + .txs + .clone() + .map(|txs| txs.into_iter().map(|tx| tx.tx).collect::>()); + + let mut l2_block: L2Block> = soft_confirmation + .clone() + .try_into() + .context("Failed to parse transactions")?; // Register this new block with the fork manager to active // the new fork on the next block self.fork_manager.register_block(l2_height)?; @@ -226,9 +230,8 @@ where Default::default(), Default::default(), current_l1_block.header(), - &mut signed_soft_confirmation, + &mut l2_block, )?; - let txs_bodies = signed_soft_confirmation.blobs().to_owned(); let next_state_root = soft_confirmation_result.state_root_transition.final_root; // Check if post state root is the same as the one in the soft confirmation @@ -254,14 +257,10 @@ where self.storage_manager.finalize_l2(l2_height)?; - let receipt = - soft_confirmation_to_receipt::(signed_soft_confirmation, current_spec); + let tx_hashes = compute_tx_hashes::(&l2_block.txs, current_spec); - self.ledger_db.commit_soft_confirmation( - next_state_root.as_ref(), - receipt, - Some(txs_bodies), - )?; + self.ledger_db + .commit_l2_block(l2_block, tx_hashes, tx_bodies)?; self.ledger_db.extend_l2_range_of_l1_slot( SlotNumber(current_l1_block.header().height()), diff --git a/crates/batch-prover/tests/prover_tests.rs b/crates/batch-prover/tests/prover_tests.rs index a5c941bd7..00fa01a3d 100644 --- a/crates/batch-prover/tests/prover_tests.rs +++ b/crates/batch-prover/tests/prover_tests.rs @@ -357,9 +357,9 @@ fn make_transition_data( time: Time::now(), bits: 0, }, - soft_confirmations: VecDeque::new(), + l2_blocks: VecDeque::new(), state_transition_witnesses: VecDeque::new(), - da_block_headers_of_soft_confirmations: VecDeque::new(), + da_block_headers_of_l2_blocks: VecDeque::new(), sequencer_public_key: vec![], sequencer_da_public_key: vec![], preproven_commitments: vec![], diff --git a/crates/citrea-stf/src/verifier.rs b/crates/citrea-stf/src/verifier.rs index cd8c1891e..98158abe7 100644 --- a/crates/citrea-stf/src/verifier.rs +++ b/crates/citrea-stf/src/verifier.rs @@ -66,7 +66,7 @@ where pre_state, da_txs, data.sequencer_commitments_range, - data.da_block_headers_of_soft_confirmations, + data.da_block_headers_of_l2_blocks, data.preproven_commitments.clone(), forks, ); diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 3d7bf29fb..17434cf48 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -15,12 +15,15 @@ resolver = "2" alloy-primitives = { workspace = true } anyhow = { workspace = true } backoff = { workspace = true } +borsh = { workspace = true } futures = { workspace = true } hex = { workspace = true } hyper = { workspace = true } jsonrpsee = { workspace = true, features = ["http-client", "server"] } lru = { workspace = true } metrics = { workspace = true } +rocksdb = { workspace = true } +rs_merkle = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } diff --git a/crates/common/src/db_migrations/mod.rs b/crates/common/src/db_migrations/mod.rs index 9b7b68966..2ff01cf57 100644 --- a/crates/common/src/db_migrations/mod.rs +++ b/crates/common/src/db_migrations/mod.rs @@ -1,7 +1,9 @@ mod batch_and_slot_by_numbers; mod remove_unused_common_tables; +mod soft_confirmation_tx_merkle_root; mod verified_proofs; pub use batch_and_slot_by_numbers::MigrateBatchAndSlotByNumber; pub use remove_unused_common_tables::RemoveUnusedTables; +pub use soft_confirmation_tx_merkle_root::MigrateSoftConfirmationTxMerkleRoot; pub use verified_proofs::MigrateVerifiedProofsBySlotNumber; diff --git a/crates/common/src/db_migrations/soft_confirmation_tx_merkle_root.rs b/crates/common/src/db_migrations/soft_confirmation_tx_merkle_root.rs new file mode 100644 index 000000000..4a8914af1 --- /dev/null +++ b/crates/common/src/db_migrations/soft_confirmation_tx_merkle_root.rs @@ -0,0 +1,120 @@ +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use borsh::BorshDeserialize; +use rocksdb::WriteBatch; +use sov_db::ledger_db::migrations::{LedgerMigration, MigrationName, MigrationVersion}; +use sov_db::ledger_db::LedgerDB; +use sov_db::schema::tables::SoftConfirmationByNumber; +use sov_db::schema::types::soft_confirmation::{StoredSoftConfirmation, StoredTransaction}; +use sov_db::schema::types::DbHash; + +use crate::utils::compute_tx_merkle_root; + +#[derive(Debug, PartialEq, BorshDeserialize)] +struct StoredSoftConfirmationV1 { + pub l2_height: u64, + pub da_slot_height: u64, + pub da_slot_hash: [u8; 32], + pub da_slot_txs_commitment: [u8; 32], + pub hash: DbHash, + pub prev_hash: DbHash, + pub txs: Vec, + pub deposit_data: Vec>, + pub state_root: [u8; 32], + pub soft_confirmation_signature: Vec, + pub pub_key: Vec, + pub l1_fee_rate: u128, + pub timestamp: u64, +} + +/// Add tx_merkle_root to StoredSoftConfirmation +pub struct MigrateSoftConfirmationTxMerkleRoot; + +impl LedgerMigration for MigrateSoftConfirmationTxMerkleRoot { + fn identifier(&self) -> (MigrationName, MigrationVersion) { + ("MigrateSoftConfirmationTxMerkleRoot".to_owned(), 4) + } + + fn execute( + &self, + ledger_db: Arc, + _tables_to_drop: &mut Vec, + ) -> anyhow::Result<()> { + let cf = ledger_db.get_cf_handle(SoftConfirmationByNumber::table_name())?; + + let mut total_entries = 0; + + let mut batch = WriteBatch::default(); + let mut count = 0; + let batch_size = 10_000; + + let mut migrate_from_iterator = ledger_db.get_iterator_for_cf(cf, None)?; + + let mut merkle_time_acc = Duration::default(); + + // Iterate from end to get total number of entry on first value + migrate_from_iterator.set_mode(rocksdb::IteratorMode::End); + + for key_value_res in migrate_from_iterator { + let (key, value) = key_value_res.unwrap(); + + let v: StoredSoftConfirmationV1 = borsh::from_slice(&value).unwrap(); + + // Get total_entries from first value since we iterate from end + if (total_entries) == 0 { + total_entries = v.l2_height; + } + + let merkle_start = Instant::now(); + let leaves: Vec<[u8; 32]> = v.txs.iter().map(|tx| tx.hash).collect(); + let tx_merkle_root = compute_tx_merkle_root(&leaves)?; + + merkle_time_acc += merkle_start.elapsed(); + + let stored_conf = StoredSoftConfirmation { + l2_height: v.l2_height, + da_slot_height: v.da_slot_height, + da_slot_hash: v.da_slot_hash, + da_slot_txs_commitment: v.da_slot_txs_commitment, + hash: v.hash, + prev_hash: v.prev_hash, + txs: v.txs, + deposit_data: v.deposit_data, + state_root: v.state_root, + soft_confirmation_signature: v.soft_confirmation_signature, + pub_key: v.pub_key, + l1_fee_rate: v.l1_fee_rate, + timestamp: v.timestamp, + tx_merkle_root, + }; + + let new_value = borsh::to_vec(&stored_conf)?; + + batch.put_cf(cf, key, new_value); + + count += 1; + + if count % batch_size == 0 { + let progress = (count as f64 / total_entries as f64) * 100.0; + let avg_merkle = merkle_time_acc.as_micros() as f64 / count as f64; + tracing::info!( + "Progress: {}/{} ({:.2}%) | Avg merkle time: {:.2}µs", + count, + total_entries, + progress, + avg_merkle + ); + + ledger_db.write(batch)?; + batch = WriteBatch::default(); + } + } + + if count % batch_size != 0 { + ledger_db.write(batch)?; + } + + Ok(()) + } +} diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs index 928e405e5..f9e451338 100644 --- a/crates/common/src/utils.rs +++ b/crates/common/src/utils.rs @@ -1,14 +1,18 @@ use std::collections::{HashMap, HashSet}; +use anyhow::Context as _; +use borsh::BorshSerialize; +use citrea_primitives::EMPTY_TX_ROOT; +use rs_merkle::algorithms::Sha256; +use rs_merkle::MerkleTree; use sov_db::ledger_db::SharedLedgerOps; use sov_db::schema::types::SoftConfirmationNumber; use sov_modules_api::{Context, Spec}; -use sov_rollup_interface::da::{DaSpec, SequencerCommitment}; +use sov_rollup_interface::da::SequencerCommitment; use sov_rollup_interface::digest::Digest; use sov_rollup_interface::rpc::SoftConfirmationStatus; -use sov_rollup_interface::soft_confirmation::SignedSoftConfirmation; use sov_rollup_interface::spec::SpecId; -use sov_rollup_interface::stf::{SoftConfirmationReceipt, StateDiff, TransactionDigest}; +use sov_rollup_interface::stf::{StateDiff, TransactionDigest}; pub fn merge_state_diffs(old_diff: StateDiff, new_diff: StateDiff) -> StateDiff { let mut new_diff_map = HashMap::, Option>>::from_iter(old_diff); @@ -79,36 +83,30 @@ pub fn check_l2_block_exists(ledger_db: &DB, l2_height: u64 head_l2_height >= l2_height } -pub fn soft_confirmation_to_receipt( - soft_confirmation: SignedSoftConfirmation<'_, Tx>, +pub fn compute_tx_hashes( + txs: &[Tx], current_spec: SpecId, -) -> SoftConfirmationReceipt { - let tx_hashes = if current_spec >= SpecId::Kumquat { - soft_confirmation - .txs() - .iter() +) -> Vec<[u8; 32]> { + if current_spec >= SpecId::Kumquat { + txs.iter() .map(|tx| tx.compute_digest::<::Hasher>().into()) .collect() } else { - soft_confirmation - .blobs() - .iter() - .map(|raw_tx| ::Hasher::digest(raw_tx).into()) + txs.iter() + .map(|tx| { + let serialized = borsh::to_vec(tx).expect("Tx serialization shouldn't fail"); + ::Hasher::digest(&serialized).into() + }) .collect() - }; + } +} - SoftConfirmationReceipt { - l2_height: soft_confirmation.l2_height(), - hash: soft_confirmation.hash(), - prev_hash: soft_confirmation.prev_hash(), - da_slot_height: soft_confirmation.da_slot_height(), - da_slot_hash: soft_confirmation.da_slot_hash().into(), - da_slot_txs_commitment: soft_confirmation.da_slot_txs_commitment().into(), - l1_fee_rate: soft_confirmation.l1_fee_rate(), - tx_hashes, - deposit_data: soft_confirmation.deposit_data().to_vec(), - timestamp: soft_confirmation.timestamp(), - soft_confirmation_signature: soft_confirmation.signature().to_vec(), - pub_key: soft_confirmation.pub_key().to_vec(), +pub fn compute_tx_merkle_root(tx_hashes: &[[u8; 32]]) -> anyhow::Result<[u8; 32]> { + if tx_hashes.is_empty() { + return Ok(EMPTY_TX_ROOT); } + + MerkleTree::::from_leaves(tx_hashes) + .root() + .context("Couldn't compute merkle root") } diff --git a/crates/fullnode/src/da_block_handler.rs b/crates/fullnode/src/da_block_handler.rs index cdef3173a..ec7799707 100644 --- a/crates/fullnode/src/da_block_handler.rs +++ b/crates/fullnode/src/da_block_handler.rs @@ -254,7 +254,7 @@ where )?; for i in start_l2_height..=end_l2_height { - self.ledger_db.put_soft_confirmation_status( + self.ledger_db.put_l2_block_status( SoftConfirmationNumber(i), SoftConfirmationStatus::Finalized, )?; @@ -396,7 +396,7 @@ where let l2_start_height = commitment.l2_start_block_number; let l2_end_height = commitment.l2_end_block_number; for i in l2_start_height..=l2_end_height { - self.ledger_db.put_soft_confirmation_status( + self.ledger_db.put_l2_block_status( SoftConfirmationNumber(i), SoftConfirmationStatus::Proven, )?; diff --git a/crates/fullnode/src/db_migrations/mod.rs b/crates/fullnode/src/db_migrations/mod.rs index 1880db578..dad03a202 100644 --- a/crates/fullnode/src/db_migrations/mod.rs +++ b/crates/fullnode/src/db_migrations/mod.rs @@ -1,7 +1,8 @@ use std::sync::OnceLock; use citrea_common::db_migrations::{ - MigrateBatchAndSlotByNumber, MigrateVerifiedProofsBySlotNumber, RemoveUnusedTables, + MigrateBatchAndSlotByNumber, MigrateSoftConfirmationTxMerkleRoot, + MigrateVerifiedProofsBySlotNumber, RemoveUnusedTables, }; use sov_db::ledger_db::migrations::LedgerMigration; use sov_db::schema::tables::FULL_NODE_LEDGER_TABLES; @@ -21,6 +22,7 @@ pub fn migrations() -> &'static Vec> = - soft_confirmation - .clone() - .try_into() - .context("Failed to parse transactions")?; + let tx_bodies = soft_confirmation + .txs + .clone() + .map(|txs| txs.into_iter().map(|tx| tx.tx).collect::>()); + let mut l2_block: L2Block> = soft_confirmation + .clone() + .try_into() + .context("Failed to parse transactions")?; // Register this new block with the fork manager to active // the new fork on the next block. @@ -158,7 +161,7 @@ where Default::default(), Default::default(), current_l1_block.header(), - &mut signed_soft_confirmation, + &mut l2_block, )?; let next_state_root = soft_confirmation_result.state_root_transition.final_root; @@ -172,17 +175,15 @@ where self.storage_manager.finalize_l2(l2_height)?; + let tx_hashes = compute_tx_hashes::(&l2_block.txs, current_spec); let tx_bodies = if self.include_tx_body { - Some(signed_soft_confirmation.blobs().to_owned()) + tx_bodies } else { None }; - let receipt = - soft_confirmation_to_receipt::(signed_soft_confirmation, current_spec); - self.ledger_db - .commit_soft_confirmation(next_state_root.as_ref(), receipt, tx_bodies)?; + .commit_l2_block(l2_block, tx_hashes, tx_bodies)?; self.ledger_db.extend_l2_range_of_l1_slot( SlotNumber(current_l1_block.header().height()), diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index 78a8c6a7b..bccf9dd52 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -20,3 +20,10 @@ pub const MIN_BASE_FEE_PER_GAS: u128 = 10_000_000; // 0.01 gwei pub const MAX_TXBODY_SIZE: usize = 39700; #[cfg(not(feature = "testing"))] pub const MAX_TXBODY_SIZE: usize = 397000; + +/// SHA-256 hash of "citrea" string +/// Used as the default tx merkle root when the block has no transactions +pub const EMPTY_TX_ROOT: [u8; 32] = [ + 0xb9, 0x38, 0x83, 0x52, 0xdd, 0xd5, 0x9e, 0x59, 0xf6, 0x7a, 0x20, 0x8c, 0xbe, 0xba, 0xb3, 0xcd, + 0x6b, 0x23, 0xf9, 0x62, 0xa9, 0x03, 0x2e, 0xfe, 0x78, 0x58, 0xcd, 0x84, 0x01, 0x38, 0xaa, 0x27, +]; diff --git a/crates/primitives/src/forks.rs b/crates/primitives/src/forks.rs index c0b38e449..2a4d4bc15 100644 --- a/crates/primitives/src/forks.rs +++ b/crates/primitives/src/forks.rs @@ -53,16 +53,18 @@ pub fn fork_from_block_number(block_number: u64) -> Fork { forks[pos] } -pub const MAINNET_FORKS: [Fork; 1] = [Fork::new(SpecId::Kumquat, 0)]; +pub const MAINNET_FORKS: [Fork; 1] = [Fork::new(SpecId::Fork2, 0)]; -pub const TESTNET_FORKS: [Fork; 2] = [ +pub const TESTNET_FORKS: [Fork; 3] = [ Fork::new(SpecId::Genesis, 0), Fork::new(SpecId::Kumquat, 5546000), // will be reached Jan 24 2025 ~7 PM + Fork::new(SpecId::Fork2, u64::MAX), ]; -pub const DEVNET_FORKS: [Fork; 2] = [ +pub const DEVNET_FORKS: [Fork; 3] = [ Fork::new(SpecId::Genesis, 0), Fork::new(SpecId::Kumquat, 1921835), + Fork::new(SpecId::Fork2, u64::MAX), ]; pub const NIGHTLY_FORKS: [Fork; 1] = [Fork::new(SpecId::Fork2, 0)]; diff --git a/crates/sequencer/src/db_migrations/mod.rs b/crates/sequencer/src/db_migrations/mod.rs index 5df7c5601..c7432f6f1 100644 --- a/crates/sequencer/src/db_migrations/mod.rs +++ b/crates/sequencer/src/db_migrations/mod.rs @@ -1,7 +1,8 @@ use std::sync::OnceLock; use citrea_common::db_migrations::{ - MigrateBatchAndSlotByNumber, MigrateVerifiedProofsBySlotNumber, RemoveUnusedTables, + MigrateBatchAndSlotByNumber, MigrateSoftConfirmationTxMerkleRoot, + MigrateVerifiedProofsBySlotNumber, RemoveUnusedTables, }; use sov_db::ledger_db::migrations::LedgerMigration; use sov_db::schema::tables::SEQUENCER_LEDGER_TABLES; @@ -16,6 +17,7 @@ pub fn migrations() -> &'static Vec, state_root: StorageRootHash, soft_confirmation_hash: SoftConfirmationHash, - sequencer_pub_key: Vec, + _sequencer_pub_key: Vec, sequencer_da_pub_key: Vec, fork_manager: ForkManager<'static>, soft_confirmation_tx: broadcast::Sender, @@ -130,7 +133,7 @@ where storage_manager, state_root: init_params.state_root, soft_confirmation_hash: init_params.batch_hash, - sequencer_pub_key: public_keys.sequencer_public_key, + _sequencer_pub_key: public_keys.sequencer_public_key, sequencer_da_pub_key: public_keys.sequencer_da_pub_key, fork_manager, soft_confirmation_tx, @@ -156,69 +159,70 @@ where tracing::subscriber::with_default(silent_subscriber, || { let mut working_set_to_discard = WorkingSet::new(prestate.clone()); - match self.stf.begin_soft_confirmation( + if let Err(err) = self.stf.begin_soft_confirmation( pub_key, &mut working_set_to_discard, &da_block_header, &soft_confirmation_info, ) { - Ok(_) => { - match l2_block_mode { - L2BlockMode::NotEmpty => { - // Normally, transactions.mark_invalid() calls would give us the same - // functionality as invalid_senders, however, - // in this version of reth, mark_invalid uses transaction.hash() to mark invalid - // which is not desired. This was fixed in later versions, but we can not update - // to those versions because we have to lock our Rust version to 1.81. - // - // When a tx is rejected, its sender is added to invalid_senders set - // because other transactions from the same sender now cannot be included in the block - // since they are auto rejected due to the nonce gap. - let mut invalid_senders = HashSet::new(); - - let mut all_txs = vec![]; - let mut l1_fee_failed_txs = vec![]; - - // using .next() instead of a for loop because its the intended - // behaviour for the BestTransactions implementations - // when we update reth we'll need to call transactions.mark_invalid() - #[allow(clippy::while_let_on_iterator)] - while let Some(evm_tx) = transactions.next() { - if invalid_senders.contains(&evm_tx.transaction_id.sender) { - continue; - } - - let mut buf = vec![]; - evm_tx - .to_recovered_transaction() - .into_signed() - .encode_2718(&mut buf); - let rlp_tx = RlpEvmTransaction { rlp: buf }; - - let call_txs = CallMessage { - txs: vec![rlp_tx.clone()], - }; - let raw_message = as EncodeCall< - citrea_evm::Evm, - >>::encode_call( - call_txs - ); - let signed_blob = self - .make_blob(raw_message.clone(), &mut working_set_to_discard)?; + warn!( + "DryRun: Failed to apply soft confirmation hook: {:?} \n reverting batch workspace", + err + ); + bail!( + "DryRun: Failed to apply begin soft confirmation hook: {:?}", + err + ) + } + + match l2_block_mode { + L2BlockMode::NotEmpty => { + // Normally, transactions.mark_invalid() calls would give us the same + // functionality as invalid_senders, however, + // in this version of reth, mark_invalid uses transaction.hash() to mark invalid + // which is not desired. This was fixed in later versions, but we can not update + // to those versions because we have to lock our Rust version to 1.81. + // + // When a tx is rejected, its sender is added to invalid_senders set + // because other transactions from the same sender now cannot be included in the block + // since they are auto rejected due to the nonce gap. + let mut invalid_senders = HashSet::new(); + + let mut all_txs = vec![]; + let mut l1_fee_failed_txs = vec![]; + + // using .next() instead of a for loop because its the intended + // behaviour for the BestTransactions implementations + // when we update reth we'll need to call transactions.mark_invalid() + #[allow(clippy::while_let_on_iterator)] + while let Some(evm_tx) = transactions.next() { + if invalid_senders.contains(&evm_tx.transaction_id.sender) { + continue; + } - let signed_tx = - self.sign_tx(raw_message, &mut working_set_to_discard)?; + let mut buf = vec![]; + evm_tx + .to_recovered_transaction() + .into_signed() + .encode_2718(&mut buf); + let rlp_tx = RlpEvmTransaction { rlp: buf }; - let txs = vec![signed_blob.clone()]; - let txs_new = vec![signed_tx]; + let call_txs = CallMessage { + txs: vec![rlp_tx.clone()], + }; + let raw_message = as EncodeCall< + citrea_evm::Evm, + >>::encode_call(call_txs); - let mut working_set = - working_set_to_discard.checkpoint().to_revertable(); + let signed_tx = self.sign_tx(raw_message, &mut working_set_to_discard)?; - match self.stf.apply_soft_confirmation_txs( - soft_confirmation_info.clone(), + let txs = vec![signed_tx]; + + let mut working_set = working_set_to_discard.checkpoint().to_revertable(); + + match self.stf.apply_soft_confirmation_txs( + &soft_confirmation_info, &txs, - &txs_new, &mut working_set, ) { Ok(result) => result, @@ -269,32 +273,20 @@ where }, }; - // if no errors - // we can include the transaction in the block - working_set_to_discard = working_set.checkpoint().to_revertable(); - all_txs.push(rlp_tx); - } - SEQUENCER_METRICS.dry_run_execution.record( - Instant::now() - .saturating_duration_since(start) - .as_secs_f64(), - ); - - Ok((all_txs, l1_fee_failed_txs)) - } - L2BlockMode::Empty => Ok((vec![], vec![])), + // if no errors + // we can include the transaction in the block + working_set_to_discard = working_set.checkpoint().to_revertable(); + all_txs.push(rlp_tx); } + SEQUENCER_METRICS.dry_run_execution.record( + Instant::now() + .saturating_duration_since(start) + .as_secs_f64(), + ); + + Ok((all_txs, l1_fee_failed_txs)) } - Err(err) => { - warn!( - "DryRun: Failed to apply soft confirmation hook: {:?} \n reverting batch workspace", - err - ); - Err(anyhow!( - "DryRun: Failed to apply begin soft confirmation hook: {:?}", - err - )) - } + L2BlockMode::Empty => Ok((vec![], vec![])), } }) } @@ -378,168 +370,143 @@ where let mut working_set = WorkingSet::new(prestate.clone()); // Execute the selected transactions - match self.stf.begin_soft_confirmation( + if let Err(err) = self.stf.begin_soft_confirmation( &pub_key, &mut working_set, da_block.header(), &soft_confirmation_info, ) { - Ok(_) => { - let mut txs = vec![]; - let mut txs_new = vec![]; - - let evm_txs_count = txs_to_run.len(); - if evm_txs_count > 0 { - let call_txs = CallMessage { txs: txs_to_run }; - let raw_message = - as EncodeCall>>::encode_call( - call_txs, - ); - let signed_blob = self.make_blob(raw_message.clone(), &mut working_set)?; - let signed_tx = self.sign_tx(raw_message, &mut working_set)?; - txs.push(signed_blob); - txs_new.push(signed_tx); - - self.stf - .apply_soft_confirmation_txs( - soft_confirmation_info, - &txs, - &txs_new, - &mut working_set, - ) - .expect("dry_run_transactions should have already checked this"); - } + warn!( + "Failed to apply soft confirmation hook: {:?} \n reverting batch workspace", + err + ); + bail!("Failed to apply begin soft confirmation hook: {:?}", err) + }; - // create the unsigned batch with the txs then sign th sc - let unsigned_batch = UnsignedSoftConfirmation::new( - l2_height, - da_block.header().height(), - da_block.header().hash().into(), - da_block.header().txs_commitment().into(), - &txs, - &txs_new, - deposit_data, - l1_fee_rate, - timestamp, - ); + let mut blobs = vec![]; + let mut txs = vec![]; + + let evm_txs_count = txs_to_run.len(); + if evm_txs_count > 0 { + let call_txs = CallMessage { txs: txs_to_run }; + let raw_message = + as EncodeCall>>::encode_call(call_txs); + let signed_blob = self.make_blob(raw_message.clone(), &mut working_set)?; + let signed_tx = self.sign_tx(raw_message, &mut working_set)?; + blobs.push(signed_blob); + txs.push(signed_tx); + + self.stf + .apply_soft_confirmation_txs(&soft_confirmation_info, &txs, &mut working_set) + .expect("dry_run_transactions should have already checked this"); + } - let mut signed_soft_confirmation = if active_fork_spec - >= sov_modules_api::SpecId::Kumquat - { - self.sign_soft_confirmation_batch(&unsigned_batch, self.soft_confirmation_hash)? - } else { - self.pre_fork1_sign_soft_confirmation_batch( - &unsigned_batch, - self.soft_confirmation_hash, - )? - }; + self.stf + .end_soft_confirmation(soft_confirmation_info, &mut working_set)?; - self.stf.end_soft_confirmation( - active_fork_spec, - self.state_root, - self.sequencer_pub_key.as_ref(), - &mut signed_soft_confirmation, - &mut working_set, - )?; - - // Finalize soft confirmation - let soft_confirmation_result = self.stf.finalize_soft_confirmation( - active_fork_spec, - working_set, - prestate, - &mut signed_soft_confirmation, - ); - let state_root_transition = soft_confirmation_result.state_root_transition; + // Finalize soft confirmation + let soft_confirmation_result = + self.stf + .finalize_soft_confirmation(active_fork_spec, working_set, prestate); - if state_root_transition.final_root.as_ref() == self.state_root.as_ref() { - bail!("Max L2 blocks per L1 is reached for the current L1 block. State root is the same as before, skipping"); - } + // Calculate tx hashes for merkle root + let tx_hashes = compute_tx_hashes::(&txs, active_fork_spec); + let tx_merkle_root = compute_tx_merkle_root(&tx_hashes)?; - trace!( - "State root after applying slot: {:?}", - state_root_transition.final_root, - ); + // create the soft confirmation header + let header = SoftConfirmationHeader::new( + l2_height, + da_block.header().height(), + da_block.header().hash().into(), + da_block.header().txs_commitment().into(), + self.soft_confirmation_hash, + soft_confirmation_result.state_root_transition.final_root, + l1_fee_rate, + tx_merkle_root, + deposit_data.clone(), + timestamp, + ); - let next_state_root = state_root_transition.final_root; + let l2_block = self.sign_soft_confirmation(active_fork_spec, header, &blobs, &txs)?; - self.storage_manager - .save_change_set_l2(l2_height, soft_confirmation_result.change_set)?; + debug!( + "soft confirmation with hash: {:?} from sequencer {:?} has been successfully applied", + hex::encode(l2_block.hash()), + hex::encode(l2_block.sequencer_pub_key()), + ); - // TODO: this will only work for mock da - // when https://github.com/Sovereign-Labs/sovereign-sdk/issues/1218 - // is merged, rpc will access up to date storage then we won't need to finalize right away. - // however we need much better DA + finalization logic here - self.storage_manager.finalize_l2(l2_height)?; + let state_root_transition = soft_confirmation_result.state_root_transition; - let tx_bodies = signed_soft_confirmation.blobs().to_owned(); - let soft_confirmation_hash = signed_soft_confirmation.hash(); - let receipt = soft_confirmation_to_receipt::( - signed_soft_confirmation, - active_fork_spec, - ); - self.ledger_db.commit_soft_confirmation( - next_state_root.as_ref(), - receipt, - Some(tx_bodies), - )?; - - // connect L1 and L2 height - self.ledger_db.extend_l2_range_of_l1_slot( - SlotNumber(da_block.header().height()), - SoftConfirmationNumber(l2_height), - )?; - - let l1_height = da_block.header().height(); - info!( - "New block #{}, DA #{}, Tx count: #{}", - l2_height, l1_height, evm_txs_count, - ); + if state_root_transition.final_root.as_ref() == self.state_root.as_ref() { + bail!("Max L2 blocks per L1 is reached for the current L1 block. State root is the same as before, skipping"); + } + + trace!( + "State root after applying slot: {:?}", + state_root_transition.final_root, + ); - self.state_root = next_state_root; - self.soft_confirmation_hash = soft_confirmation_hash; + let next_state_root = state_root_transition.final_root; - let mut txs_to_remove = self.db_provider.last_block_tx_hashes()?; - txs_to_remove.extend(l1_fee_failed_txs); + self.storage_manager + .save_change_set_l2(l2_height, soft_confirmation_result.change_set)?; - self.mempool.remove_transactions(txs_to_remove.clone()); - SEQUENCER_METRICS.mempool_txs.set(self.mempool.len() as f64); + // TODO: this will only work for mock da + // when https://github.com/Sovereign-Labs/sovereign-sdk/issues/1218 + // is merged, rpc will access up to date storage then we won't need to finalize right away. + // however we need much better DA + finalization logic here + self.storage_manager.finalize_l2(l2_height)?; - let account_updates = self.get_account_updates()?; + let soft_confirmation_hash = l2_block.hash(); - self.mempool.update_accounts(account_updates); + self.ledger_db + .commit_l2_block(l2_block, tx_hashes, Some(blobs))?; - let txs = txs_to_remove - .iter() - .map(|tx_hash| tx_hash.to_vec()) - .collect::>>(); - if let Err(e) = self.ledger_db.remove_mempool_txs(txs) { - warn!("Failed to remove txs from mempool: {:?}", e); - } + // connect L1 and L2 height + self.ledger_db.extend_l2_range_of_l1_slot( + SlotNumber(da_block.header().height()), + SoftConfirmationNumber(l2_height), + )?; - SEQUENCER_METRICS.block_production_execution.record( - Instant::now() - .saturating_duration_since(start) - .as_secs_f64(), - ); - SEQUENCER_METRICS.current_l2_block.set(l2_height as f64); + let l1_height = da_block.header().height(); + info!( + "New block #{}, DA #{}, Tx count: #{}", + l2_height, l1_height, evm_txs_count, + ); - Ok(( - l2_height, - da_block.header().height(), - soft_confirmation_result.state_diff, - )) - } - Err(err) => { - warn!( - "Failed to apply soft confirmation hook: {:?} \n reverting batch workspace", - err - ); - Err(anyhow!( - "Failed to apply begin soft confirmation hook: {:?}", - err - )) - } + self.state_root = next_state_root; + self.soft_confirmation_hash = soft_confirmation_hash; + + let mut txs_to_remove = self.db_provider.last_block_tx_hashes()?; + txs_to_remove.extend(l1_fee_failed_txs); + + self.mempool.remove_transactions(txs_to_remove.clone()); + SEQUENCER_METRICS.mempool_txs.set(self.mempool.len() as f64); + + let account_updates = self.get_account_updates()?; + + self.mempool.update_accounts(account_updates); + + let txs = txs_to_remove + .iter() + .map(|tx_hash| tx_hash.to_vec()) + .collect::>>(); + if let Err(e) = self.ledger_db.remove_mempool_txs(txs) { + warn!("Failed to remove txs from mempool: {:?}", e); } + + SEQUENCER_METRICS.block_production_execution.record( + Instant::now() + .saturating_duration_since(start) + .as_secs_f64(), + ); + SEQUENCER_METRICS.current_l2_block.set(l2_height as f64); + + Ok(( + l2_height, + da_block.header().height(), + soft_confirmation_result.state_diff, + )) } #[instrument(level = "trace", skip(self, cancellation_token), err, ret)] @@ -764,66 +731,83 @@ where Ok(tx) } + fn sign_soft_confirmation<'txs>( + &mut self, + active_spec: SpecId, + header: SoftConfirmationHeader, + blobs: &[Vec], + txs: &'txs [StfTransaction], + ) -> anyhow::Result>> { + match active_spec { + SpecId::Genesis => self.sign_soft_confirmation_batch_v1(header, blobs, txs), + SpecId::Kumquat => self.sign_soft_confirmation_batch_v2(header, blobs, txs), + _ => self.sign_soft_confirmation_header(header, txs), + } + } + + fn sign_soft_confirmation_header<'txs>( + &mut self, + header: SoftConfirmationHeader, + txs: &'txs [StfTransaction], + ) -> anyhow::Result>> { + let digest = header.compute_digest::<::Hasher>(); + let hash = Into::<[u8; 32]>::into(digest); + let signature = self.sov_tx_signer_priv_key.sign(&hash); + let pub_key = self.sov_tx_signer_priv_key.pub_key(); + + let signature = borsh::to_vec(&signature)?; + let pub_key = borsh::to_vec(&pub_key)?; + let signed_header = SignedSoftConfirmationHeader::new(header, hash, signature, pub_key); + + Ok(L2Block::new(signed_header, txs.into())) + } + /// Signs necessary info and returns a BlockTemplate - fn sign_soft_confirmation_batch<'txs>( + fn sign_soft_confirmation_batch_v2<'txs>( &mut self, - soft_confirmation: &'txs UnsignedSoftConfirmation<'_, StfTransaction>, - prev_soft_confirmation_hash: [u8; 32], - ) -> anyhow::Result>> { + header: SoftConfirmationHeader, + blobs: &[Vec], + txs: &'txs [StfTransaction], + ) -> anyhow::Result>> { + let soft_confirmation = &UnsignedSoftConfirmation::from((&header, blobs.to_vec(), txs)); + let digest = soft_confirmation.compute_digest::<::Hasher>(); let hash = Into::<[u8; 32]>::into(digest); let signature = self.sov_tx_signer_priv_key.sign(&hash); let pub_key = self.sov_tx_signer_priv_key.pub_key(); - Ok(SignedSoftConfirmation::new( - soft_confirmation.l2_height(), - hash, - prev_soft_confirmation_hash, - soft_confirmation.da_slot_height(), - soft_confirmation.da_slot_hash(), - soft_confirmation.da_slot_txs_commitment(), - soft_confirmation.l1_fee_rate(), - soft_confirmation.blobs().into(), - soft_confirmation.txs().into(), - soft_confirmation.deposit_data(), - borsh::to_vec(&signature).map_err(|e| anyhow!(e))?, - borsh::to_vec(&pub_key).map_err(|e| anyhow!(e))?, - soft_confirmation.timestamp(), - )) + let signature = borsh::to_vec(&signature)?; + let pub_key = borsh::to_vec(&pub_key)?; + let signed_header = SignedSoftConfirmationHeader::new(header, hash, signature, pub_key); + + Ok(L2Block::new(signed_header, txs.into())) } /// Old version of sign_soft_confirmation_batch /// TODO: Remove derive(BorshSerialize) for UnsignedSoftConfirmation /// when removing this fn /// FIXME: ^ - fn pre_fork1_sign_soft_confirmation_batch<'txs>( + fn sign_soft_confirmation_batch_v1<'txs>( &mut self, - soft_confirmation: &'txs UnsignedSoftConfirmation<'_, StfTransaction>, - prev_soft_confirmation_hash: [u8; 32], - ) -> anyhow::Result>> { + header: SoftConfirmationHeader, + blobs: &[Vec], + txs: &'txs [StfTransaction], + ) -> anyhow::Result>> { use digest::Digest; + let soft_confirmation = &UnsignedSoftConfirmation::from((&header, blobs.to_vec(), txs)); let raw = borsh::to_vec(&UnsignedSoftConfirmationV1::from(soft_confirmation.clone())) .map_err(|e| anyhow!(e))?; let hash = ::Hasher::digest(raw.as_slice()).into(); let signature = self.sov_tx_signer_priv_key.sign(&raw); let pub_key = self.sov_tx_signer_priv_key.pub_key(); - Ok(SignedSoftConfirmation::new( - soft_confirmation.l2_height(), - hash, - prev_soft_confirmation_hash, - soft_confirmation.da_slot_height(), - soft_confirmation.da_slot_hash(), - soft_confirmation.da_slot_txs_commitment(), - soft_confirmation.l1_fee_rate(), - soft_confirmation.blobs().into(), - soft_confirmation.txs().into(), - soft_confirmation.deposit_data(), - borsh::to_vec(&signature).map_err(|e| anyhow!(e))?, - borsh::to_vec(&pub_key).map_err(|e| anyhow!(e))?, - soft_confirmation.timestamp(), - )) + + let signature = borsh::to_vec(&signature)?; + let pub_key = borsh::to_vec(&pub_key)?; + let signed_header = SignedSoftConfirmationHeader::new(header, hash, signature, pub_key); + + Ok(L2Block::new(signed_header, txs.into())) } /// Fetches nonce from state diff --git a/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/mod.rs b/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/mod.rs index fa6c29058..3bb5d80e1 100644 --- a/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/mod.rs +++ b/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/mod.rs @@ -1,11 +1,14 @@ use std::path::Path; use std::sync::Arc; +use borsh::BorshSerialize; +use rocksdb::WriteBatch; use serde::de::DeserializeOwned; use serde::Serialize; -use sov_rollup_interface::da::{DaSpec, SequencerCommitment}; +use sov_rollup_interface::da::SequencerCommitment; use sov_rollup_interface::fork::{Fork, ForkMigration}; -use sov_rollup_interface::stf::{SoftConfirmationReceipt, StateDiff}; +use sov_rollup_interface::soft_confirmation::L2Block; +use sov_rollup_interface::stf::StateDiff; use sov_rollup_interface::zk::{Proof, StorageRootHash}; use sov_schema_db::{Schema, SchemaBatch, SeekKeyEncoder, DB}; use tracing::instrument; @@ -138,6 +141,11 @@ impl LedgerDB { _ => Ok(None), } } + + /// Write raw rocksdb WriteBatch + pub fn write(&self, batch: WriteBatch) -> anyhow::Result<()> { + self.db.write(batch) + } } impl SharedLedgerOps for LedgerDB { @@ -147,7 +155,7 @@ impl SharedLedgerOps for LedgerDB { } #[instrument(level = "trace", skip(self, schema_batch), err, ret)] - fn put_soft_confirmation( + fn put_l2_block( &self, batch: &StoredSoftConfirmation, batch_number: &SoftConfirmationNumber, @@ -158,54 +166,55 @@ impl SharedLedgerOps for LedgerDB { } /// Commits a soft confirmation to the database by inserting its transactions and batches before - fn commit_soft_confirmation( + fn commit_l2_block( &self, - state_root: &[u8], - soft_confirmation_receipt: SoftConfirmationReceipt, + l2_block: L2Block<'_, Tx>, + tx_hashes: Vec<[u8; 32]>, tx_bodies: Option>>, ) -> Result<(), anyhow::Error> { let mut schema_batch = SchemaBatch::new(); - let tx_hashes = soft_confirmation_receipt.tx_hashes.into_iter(); - let txs = match tx_bodies { - Some(tx_bodies) => { - assert_eq!( - tx_bodies.len(), - tx_hashes.len(), - "Tx body count does not match tx hash count" - ); - tx_hashes - .zip(tx_bodies) - .map(|(hash, body)| StoredTransaction { - hash, - body: Some(body), - }) - .collect::>() - } - None => tx_hashes + let txs = if let Some(tx_bodies) = tx_bodies { + assert_eq!( + tx_bodies.len(), + tx_hashes.len(), + "Tx body count does not match tx hash count" + ); + tx_hashes + .into_iter() + .zip(tx_bodies) + .map(|(hash, body)| StoredTransaction { + hash, + body: Some(body), + }) + .collect::>() + } else { + tx_hashes + .into_iter() .map(|hash| StoredTransaction { hash, body: None }) - .collect::>(), + .collect::>() }; - let l2_height = soft_confirmation_receipt.l2_height; + let l2_height = l2_block.l2_height(); // Insert soft confirmation let soft_confirmation_to_store = StoredSoftConfirmation { - da_slot_height: soft_confirmation_receipt.da_slot_height, + da_slot_height: l2_block.da_slot_height(), l2_height, - da_slot_hash: soft_confirmation_receipt.da_slot_hash.into(), - da_slot_txs_commitment: soft_confirmation_receipt.da_slot_txs_commitment.into(), - hash: soft_confirmation_receipt.hash, - prev_hash: soft_confirmation_receipt.prev_hash, + da_slot_hash: l2_block.da_slot_hash(), + da_slot_txs_commitment: l2_block.da_slot_txs_commitment(), + hash: l2_block.hash(), + prev_hash: l2_block.prev_hash(), txs, - state_root: state_root.to_vec(), - soft_confirmation_signature: soft_confirmation_receipt.soft_confirmation_signature, - pub_key: soft_confirmation_receipt.pub_key, - deposit_data: soft_confirmation_receipt.deposit_data, - l1_fee_rate: soft_confirmation_receipt.l1_fee_rate, - timestamp: soft_confirmation_receipt.timestamp, + state_root: l2_block.state_root(), + soft_confirmation_signature: l2_block.signature().to_vec(), + pub_key: l2_block.pub_key().to_vec(), + deposit_data: l2_block.deposit_data().to_vec(), + l1_fee_rate: l2_block.l1_fee_rate(), + timestamp: l2_block.timestamp(), + tx_merkle_root: l2_block.tx_merkle_root(), }; - self.put_soft_confirmation( + self.put_l2_block( &soft_confirmation_to_store, &SoftConfirmationNumber(l2_height), &mut schema_batch, @@ -252,7 +261,7 @@ impl SharedLedgerOps for LedgerDB { /// Saves a soft confirmation status for a given L1 height #[instrument(level = "trace", skip(self), err, ret)] - fn put_soft_confirmation_status( + fn put_l2_block_status( &self, height: SoftConfirmationNumber, status: sov_rollup_interface::rpc::SoftConfirmationStatus, diff --git a/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/traits.rs b/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/traits.rs index 7cf3d1890..b201a506a 100644 --- a/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/traits.rs +++ b/crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/traits.rs @@ -1,10 +1,12 @@ use std::path::Path; use anyhow::Result; +use borsh::BorshSerialize; use serde::de::DeserializeOwned; use serde::Serialize; -use sov_rollup_interface::da::{DaSpec, SequencerCommitment}; -use sov_rollup_interface::stf::{SoftConfirmationReceipt, StateDiff}; +use sov_rollup_interface::da::SequencerCommitment; +use sov_rollup_interface::soft_confirmation::L2Block; +use sov_rollup_interface::stf::StateDiff; use sov_rollup_interface::zk::{Proof, StorageRootHash}; use sov_schema_db::SchemaBatch; @@ -21,7 +23,7 @@ pub trait SharedLedgerOps { fn path(&self) -> &Path; /// Put soft confirmation to db - fn put_soft_confirmation( + fn put_l2_block( &self, batch: &StoredSoftConfirmation, batch_number: &SoftConfirmationNumber, @@ -29,10 +31,10 @@ pub trait SharedLedgerOps { ) -> Result<()>; /// Commits a soft confirmation to the database by inserting its transactions and batches before - fn commit_soft_confirmation( + fn commit_l2_block( &self, - state_root: &[u8], - sc_receipt: SoftConfirmationReceipt, + l2_block: L2Block<'_, Tx>, + tx_hashes: Vec<[u8; 32]>, tx_bodies: Option>>, ) -> Result<()>; @@ -50,7 +52,7 @@ pub trait SharedLedgerOps { fn get_l1_height_of_l1_hash(&self, hash: [u8; 32]) -> Result>; /// Saves a soft confirmation status for a given L1 height - fn put_soft_confirmation_status( + fn put_l2_block_status( &self, height: SoftConfirmationNumber, status: sov_rollup_interface::rpc::SoftConfirmationStatus, diff --git a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types/soft_confirmation.rs b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types/soft_confirmation.rs index 15e54185d..30edf5b00 100644 --- a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types/soft_confirmation.rs +++ b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types/soft_confirmation.rs @@ -2,7 +2,10 @@ use std::fmt::Debug; use borsh::{BorshDeserialize, BorshSerialize}; use sov_rollup_interface::rpc::{HexTx, SoftConfirmationResponse}; -use sov_rollup_interface::soft_confirmation::SignedSoftConfirmation; +use sov_rollup_interface::soft_confirmation::{ + L2Block, SignedSoftConfirmationHeader, SoftConfirmationHeader, +}; +use sov_rollup_interface::zk::StorageRootHash; use super::DbHash; @@ -27,7 +30,7 @@ pub struct StoredSoftConfirmation { /// Deposit data coming from the L1 chain pub deposit_data: Vec>, /// State root - pub state_root: Vec, + pub state_root: StorageRootHash, /// Sequencer signature pub soft_confirmation_signature: Vec, /// Sequencer public key @@ -36,11 +39,13 @@ pub struct StoredSoftConfirmation { pub l1_fee_rate: u128, /// Sequencer's block timestamp pub timestamp: u64, + /// Transactions merkle root + pub tx_merkle_root: [u8; 32], } -impl<'txs, Tx> TryFrom for SignedSoftConfirmation<'txs, Tx> +impl<'txs, Tx> TryFrom for L2Block<'txs, Tx> where - Tx: Clone + BorshDeserialize, + Tx: Clone + BorshDeserialize + BorshSerialize, { type Error = borsh::io::Error; fn try_from(val: StoredSoftConfirmation) -> Result { @@ -52,21 +57,26 @@ where borsh::from_slice::(body) }) .collect::, Self::Error>>()?; - let res = SignedSoftConfirmation::new( + let header = SoftConfirmationHeader::new( val.l2_height, - val.hash, - val.prev_hash, val.da_slot_height, val.da_slot_hash, val.da_slot_txs_commitment, + val.prev_hash, + val.state_root, val.l1_fee_rate, - val.txs.into_iter().map(|tx| tx.body.unwrap()).collect(), - parsed_txs.into(), + val.tx_merkle_root, val.deposit_data, + val.timestamp, + ); + let signed_header = SignedSoftConfirmationHeader::new( + header, + val.hash, val.soft_confirmation_signature, val.pub_key, - val.timestamp, ); + + let res = L2Block::new(signed_header, parsed_txs.into()); Ok(res) } } @@ -98,6 +108,7 @@ impl TryFrom for SoftConfirmationResponse { .collect(), l1_fee_rate: value.l1_fee_rate, timestamp: value.timestamp, + tx_merkle_root: value.tx_merkle_root, }) } } diff --git a/crates/sovereign-sdk/full-node/db/sov-schema-db/src/lib.rs b/crates/sovereign-sdk/full-node/db/sov-schema-db/src/lib.rs index a5e248eff..9dd1e29fa 100644 --- a/crates/sovereign-sdk/full-node/db/sov-schema-db/src/lib.rs +++ b/crates/sovereign-sdk/full-node/db/sov-schema-db/src/lib.rs @@ -30,7 +30,7 @@ use anyhow::format_err; pub use iterator::{RawDbReverseIterator, ScanDirection, SchemaIterator, SeekKeyEncoder}; pub use rocksdb; pub use rocksdb::DEFAULT_COLUMN_FAMILY_NAME; -use rocksdb::{DBIterator, ReadOptions}; +use rocksdb::{DBIterator, ReadOptions, WriteBatch}; use thiserror::Error; use tracing::info; @@ -344,6 +344,11 @@ impl DB { Ok(()) } + /// Write raw rocksdb WriteBatch + pub fn write(&self, batch: WriteBatch) -> anyhow::Result<()> { + Ok(self.inner.write(batch)?) + } + /// Returns the handle for a rocksdb column family. pub fn get_cf_handle(&self, cf_name: &str) -> anyhow::Result<&rocksdb::ColumnFamily> { self.inner.cf_handle(cf_name).ok_or_else(|| { diff --git a/crates/sovereign-sdk/module-system/sov-modules-api/src/hooks.rs b/crates/sovereign-sdk/module-system/sov-modules-api/src/hooks.rs index bd2058635..63408fb45 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-api/src/hooks.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-api/src/hooks.rs @@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use sov_modules_core::{AccessoryWorkingSet, Context, Spec, WorkingSet}; use sov_rollup_interface::da::DaSpec; -use sov_rollup_interface::soft_confirmation::SignedSoftConfirmation; +use sov_rollup_interface::soft_confirmation::L2Block; use sov_rollup_interface::spec::SpecId; pub use sov_rollup_interface::stf::SoftConfirmationError; use sov_rollup_interface::stf::SoftConfirmationHookError; @@ -87,22 +87,22 @@ pub struct HookSoftConfirmationInfo { } impl HookSoftConfirmationInfo { - pub fn new( - signed_soft_confirmation: &SignedSoftConfirmation, + pub fn new( + l2_block: &L2Block, pre_state_root: StorageRootHash, current_spec: SpecId, ) -> Self { HookSoftConfirmationInfo { - l2_height: signed_soft_confirmation.l2_height(), - da_slot_height: signed_soft_confirmation.da_slot_height(), - da_slot_hash: signed_soft_confirmation.da_slot_hash(), - da_slot_txs_commitment: signed_soft_confirmation.da_slot_txs_commitment(), + l2_height: l2_block.l2_height(), + da_slot_height: l2_block.da_slot_height(), + da_slot_hash: l2_block.da_slot_hash(), + da_slot_txs_commitment: l2_block.da_slot_txs_commitment(), pre_state_root, current_spec, - pub_key: signed_soft_confirmation.sequencer_pub_key().to_vec(), - deposit_data: signed_soft_confirmation.deposit_data().to_vec(), - l1_fee_rate: signed_soft_confirmation.l1_fee_rate(), - timestamp: signed_soft_confirmation.timestamp(), + pub_key: l2_block.sequencer_pub_key().to_vec(), + deposit_data: l2_block.deposit_data().to_vec(), + l1_fee_rate: l2_block.l1_fee_rate(), + timestamp: l2_block.timestamp(), } } } diff --git a/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs b/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs index 3b0240daf..65de589b7 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs @@ -193,7 +193,7 @@ pub use sov_modules_core::{ pub use sov_rollup_interface::da::{BlobReaderTrait, DaSpec}; pub use sov_rollup_interface::services::da::SlotData; pub use sov_rollup_interface::soft_confirmation::{ - SignedSoftConfirmation, UnsignedSoftConfirmation, UnsignedSoftConfirmationV1, + L2Block, UnsignedSoftConfirmation, UnsignedSoftConfirmationV1, }; pub use sov_rollup_interface::stf::StateDiff; pub use sov_rollup_interface::zk::batch_proof::output::v2::BatchProofCircuitOutputV2; diff --git a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/Cargo.toml b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/Cargo.toml index cb251b7a6..89e68fd0c 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/Cargo.toml +++ b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/Cargo.toml @@ -27,6 +27,9 @@ sov-modules-api = { path = "../sov-modules-api", default-features = false } sov-rollup-interface = { path = "../../rollup-interface" } sov-state = { path = "../sov-state" } +# Citrea deps +citrea-primitives = { path = "../../../../crates/primitives" } + [features] default = [] native = [ diff --git a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs index 3a630c1c0..9ecf6e92f 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs @@ -1,11 +1,13 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; +use citrea_primitives::EMPTY_TX_ROOT; use itertools::Itertools; use rs_merkle::algorithms::Sha256; use rs_merkle::MerkleTree; use sov_modules_api::da::BlockHeaderTrait; +use sov_modules_api::digest::Digest; use sov_modules_api::fork::Fork; use sov_modules_api::hooks::{ ApplySoftConfirmationHooks, FinalizeHook, HookSoftConfirmationInfo, SlotHooks, TxHooks, @@ -17,11 +19,13 @@ use sov_modules_api::{ }; use sov_rollup_interface::da::DaDataBatchProof; use sov_rollup_interface::fork::ForkManager; -use sov_rollup_interface::soft_confirmation::{SignedSoftConfirmation, UnsignedSoftConfirmationV1}; +use sov_rollup_interface::soft_confirmation::{ + L2Block, SignedSoftConfirmationHeader, UnsignedSoftConfirmationV1, +}; use sov_rollup_interface::spec::SpecId; use sov_rollup_interface::stf::{ ApplySequencerCommitmentsOutput, SoftConfirmationError, SoftConfirmationResult, - StateTransitionError, StateTransitionFunction, + StateTransitionError, StateTransitionFunction, TransactionDigest, }; use sov_rollup_interface::zk::batch_proof::output::CumulativeStateDiff; use sov_rollup_interface::zk::{StorageRootHash, ZkvmGuest}; @@ -162,90 +166,82 @@ where /// Apply soft confirmation transactions pub fn apply_soft_confirmation_txs( &mut self, - soft_confirmation_info: HookSoftConfirmationInfo, - txs: &[Vec], - txs_new: &[>::Transaction], + soft_confirmation_info: &HookSoftConfirmationInfo, + txs: &[>::Transaction], batch_workspace: &mut WorkingSet, ) -> Result<(), StateTransitionError> { - self.apply_sov_txs_inner(soft_confirmation_info, txs, txs_new, batch_workspace) + self.apply_sov_txs_inner(soft_confirmation_info, txs, batch_workspace) } - /// End a soft confirmation - pub fn end_soft_confirmation( - &mut self, + /// Verify l2_block hash and signature + pub fn verify_soft_confirmation( + &self, current_spec: SpecId, - pre_state_root: StorageRootHash, + l2_block: &L2Block<>::Transaction>, sequencer_public_key: &[u8], - soft_confirmation: &mut SignedSoftConfirmation< - >::Transaction, - >, - working_set: &mut WorkingSet, ) -> Result<(), StateTransitionError> { - let unsigned = UnsignedSoftConfirmation::new( - soft_confirmation.l2_height(), - soft_confirmation.da_slot_height(), - soft_confirmation.da_slot_hash(), - soft_confirmation.da_slot_txs_commitment(), - soft_confirmation.blobs(), - soft_confirmation.txs(), - soft_confirmation.deposit_data().to_vec(), - soft_confirmation.l1_fee_rate(), - soft_confirmation.timestamp(), - ); - - // check the claimed hash - if current_spec >= SpecId::Kumquat { - let digest = unsigned.compute_digest::<::Hasher>(); - let hash = Into::<[u8; 32]>::into(digest); - if soft_confirmation.hash() != hash { - return Err(StateTransitionError::SoftConfirmationError( - SoftConfirmationError::InvalidSoftConfirmationHash, - )); - } + let l2_header = &l2_block.header; - // verify signature - if verify_soft_confirmation_signature::( - soft_confirmation, - soft_confirmation.signature(), - sequencer_public_key, + verify_tx_merkle_root::>::Transaction>( + current_spec, + l2_block, + ) + .map_err(|_| { + StateTransitionError::SoftConfirmationError( + SoftConfirmationError::InvalidateTxMerkleRoot, ) - .is_err() - { - return Err(StateTransitionError::SoftConfirmationError( - SoftConfirmationError::InvalidSoftConfirmationSignature, - )); - } - } else { - let unsigned = UnsignedSoftConfirmationV1::from(unsigned); - let digest = unsigned.hash::<::Hasher>(); - let hash = Into::<[u8; 32]>::into(digest); - if soft_confirmation.hash() != hash { - return Err(StateTransitionError::SoftConfirmationError( - SoftConfirmationError::InvalidSoftConfirmationHash, - )); + })?; + + match current_spec { + SpecId::Genesis => { + let unsigned = UnsignedSoftConfirmationV1::from(l2_block); + let raw = borsh::to_vec(&unsigned).map_err(|_| { + StateTransitionError::SoftConfirmationError( + SoftConfirmationError::NonSerializableSovTx, + ) + })?; + + let expected_hash: [u8; 32] = ::Hasher::digest(&raw).into(); + if l2_block.hash() != expected_hash { + return Err(StateTransitionError::SoftConfirmationError( + SoftConfirmationError::InvalidSoftConfirmationHash, + )); + } + + verify_genesis_signature::(&raw, &l2_header.signature, sequencer_public_key) } + _ => { + let expected_hash = if current_spec == SpecId::Kumquat { + let unsigned = UnsignedSoftConfirmation::from(l2_block); + Into::<[u8; 32]>::into(unsigned.compute_digest::<::Hasher>()) + } else { + Into::<[u8; 32]>::into(l2_header.inner.compute_digest::<::Hasher>()) + }; - // verify signature - if pre_fork1_verify_soft_confirmation_signature::( - &unsigned, - soft_confirmation.signature(), - sequencer_public_key, - ) - .is_err() - { - return Err(StateTransitionError::SoftConfirmationError( - SoftConfirmationError::InvalidSoftConfirmationSignature, - )); + if l2_block.hash() != expected_hash { + return Err(StateTransitionError::SoftConfirmationError( + SoftConfirmationError::InvalidSoftConfirmationHash, + )); + } + + verify_soft_confirmation_signature::(l2_header, sequencer_public_key) } - }; + } + .map_err(|_| { + StateTransitionError::SoftConfirmationError( + SoftConfirmationError::InvalidSoftConfirmationSignature, + ) + }) + } - self.end_soft_confirmation_inner( - current_spec, - pre_state_root, - soft_confirmation, - working_set, - ) - .map_err(StateTransitionError::HookError) + /// End a soft confirmation + pub fn end_soft_confirmation( + &mut self, + soft_confirmation_info: HookSoftConfirmationInfo, + working_set: &mut WorkingSet, + ) -> Result<(), StateTransitionError> { + self.end_soft_confirmation_inner(soft_confirmation_info, working_set) + .map_err(StateTransitionError::HookError) } /// Finalizes a soft confirmation @@ -254,16 +250,7 @@ where _current_spec: SpecId, working_set: WorkingSet, pre_state: >::PreState, - soft_confirmation: &mut SignedSoftConfirmation< - >::Transaction, - >, ) -> SoftConfirmationResult::Witness> { - native_debug!( - "soft confirmation with hash: {:?} from sequencer {:?} successfully applied", - hex::encode(soft_confirmation.hash()), - hex::encode(soft_confirmation.sequencer_pub_key()), - ); - let (state_root_transition, witness, offchain_witness, storage, state_diff) = { // Save checkpoint let mut checkpoint = working_set.checkpoint(); @@ -368,10 +355,10 @@ where // the header hash does not need to be verified here because the full // nodes construct the header on their own slot_header: &::BlockHeader, - soft_confirmation: &mut SignedSoftConfirmation, + l2_block: &mut L2Block, ) -> Result, StateTransitionError> { let soft_confirmation_info = - HookSoftConfirmationInfo::new(soft_confirmation, *pre_state_root, current_spec); + HookSoftConfirmationInfo::new(l2_block, *pre_state_root, current_spec); let checkpoint = StateCheckpoint::with_witness(pre_state.clone(), state_witness, offchain_witness); @@ -386,29 +373,21 @@ where &soft_confirmation_info, )?; - self.apply_soft_confirmation_txs( - soft_confirmation_info, - soft_confirmation.blobs(), - soft_confirmation.txs(), - &mut working_set, - )?; + self.apply_soft_confirmation_txs(&soft_confirmation_info, &l2_block.txs, &mut working_set)?; - self.end_soft_confirmation( - current_spec, - *pre_state_root, - sequencer_public_key, - soft_confirmation, - &mut working_set, - )?; + self.verify_soft_confirmation(current_spec, l2_block, sequencer_public_key)?; - Ok( - self.finalize_soft_confirmation( - current_spec, - working_set, - pre_state, - soft_confirmation, - ), - ) + self.end_soft_confirmation(soft_confirmation_info, &mut working_set)?; + + let res = self.finalize_soft_confirmation(current_spec, working_set, pre_state); + + native_debug!( + "soft confirmation with hash: {:?} from sequencer {:?} has been successfully applied", + hex::encode(l2_block.hash()), + hex::encode(l2_block.sequencer_pub_key()), + ); + + Ok(res) } fn apply_soft_confirmations_from_sequencer_commitments( @@ -525,16 +504,15 @@ where let mut soft_confirmation_hashes = Vec::with_capacity(state_change_count as usize); for _ in 0..state_change_count { - let (mut soft_confirmation, state_witness, offchain_witness) = guest - .read_from_host::<( - SignedSoftConfirmation, - ::Witness, - ::Witness, - )>(); + let (mut l2_block, state_witness, offchain_witness) = guest.read_from_host::<( + L2Block, + ::Witness, + ::Witness, + )>(); if let Some(hash) = prev_soft_confirmation_hash { assert_eq!( - soft_confirmation.prev_hash(), + l2_block.prev_hash(), hash, "Soft confirmation previous hash must match the hash of the block before" ); @@ -542,10 +520,9 @@ where // the soft confirmations DA hash must equal to da hash in index_headers // if it's not matching, and if it's not matching the next one, then state transition is invalid. - if soft_confirmation.da_slot_hash() == da_block_headers[index_headers].hash().into() - { + if l2_block.da_slot_hash() == da_block_headers[index_headers].hash().into() { assert_eq!( - soft_confirmation.da_slot_height(), + l2_block.da_slot_height(), da_block_headers[index_headers].height(), "Soft confirmation DA slot height must match DA block header height" ); @@ -576,20 +553,20 @@ where // if the next one is not matching, then the state transition is invalid. assert_eq!( - soft_confirmation.da_slot_hash(), + l2_block.da_slot_hash(), da_block_headers[index_headers].hash().into(), "Soft confirmation DA slot hash must match DA block header hash" ); assert_eq!( - soft_confirmation.da_slot_height(), + l2_block.da_slot_height(), da_block_headers[index_headers].height(), "Soft confirmation DA slot height must match DA block header height" ); } assert_eq!( - soft_confirmation.l2_height(), + l2_block.l2_height(), l2_height, "Soft confirmation heights not sequential" ); @@ -609,7 +586,7 @@ where state_witness, offchain_witness, &da_block_headers[index_headers], - &mut soft_confirmation, + &mut l2_block, ) // TODO: this can be just ignoring the failing seq. com. // We can count a failed soft confirmation as a valid state transition. @@ -622,9 +599,9 @@ where l2_height += 1; - prev_soft_confirmation_hash = Some(soft_confirmation.hash()); + prev_soft_confirmation_hash = Some(l2_block.hash()); - soft_confirmation_hashes.push(soft_confirmation.hash()); + soft_confirmation_hashes.push(l2_block.hash()); } assert_eq!( @@ -661,40 +638,62 @@ where } } -fn verify_soft_confirmation_signature( - signed_soft_confirmation: &SignedSoftConfirmation, - signature: &[u8], +fn verify_soft_confirmation_signature( + header: &SignedSoftConfirmationHeader, sequencer_public_key: &[u8], -) -> Result<(), anyhow::Error> { - let message = signed_soft_confirmation.hash(); - - let signature = C::Signature::try_from(signature)?; - - signature.verify( - &C::PublicKey::try_from(sequencer_public_key)?, - message.as_slice(), - )?; +) -> anyhow::Result<()> { + let signature = C::Signature::try_from(&header.signature)?; + let public_key = C::PublicKey::try_from(sequencer_public_key)?; + signature.verify(&public_key, &header.hash)?; Ok(()) } -// Old version of verify_soft_confirmation_signature -// TODO: Remove derive(BorshSerialize) for UnsignedSoftConfirmation -// when removing this fn -// FIXME: ^ -fn pre_fork1_verify_soft_confirmation_signature( - unsigned_soft_confirmation: &UnsignedSoftConfirmationV1, +fn verify_genesis_signature( + message: &[u8], signature: &[u8], sequencer_public_key: &[u8], -) -> Result<(), anyhow::Error> { - let message = borsh::to_vec(&unsigned_soft_confirmation).unwrap(); - +) -> anyhow::Result<()> { let signature = C::Signature::try_from(signature)?; + let public_key = C::PublicKey::try_from(sequencer_public_key)?; - signature.verify( - &C::PublicKey::try_from(sequencer_public_key)?, - message.as_slice(), - )?; + signature.verify(&public_key, message)?; + Ok(()) +} +fn verify_tx_merkle_root( + current_spec: SpecId, + l2_block: &L2Block<'_, Tx>, +) -> Result<(), StateTransitionError> { + let tx_hashes: Vec<[u8; 32]> = if current_spec >= SpecId::Kumquat { + l2_block + .txs + .iter() + .map(|tx| tx.compute_digest::<::Hasher>().into()) + .collect() + } else { + l2_block + .txs + .iter() + .map(|tx| { + let serialized = borsh::to_vec(tx).expect("Tx serialization shouldn't fail"); + ::Hasher::digest(&serialized).into() + }) + .collect() + }; + + let tx_merkle_root = if tx_hashes.is_empty() { + EMPTY_TX_ROOT + } else { + MerkleTree::::from_leaves(&tx_hashes) + .root() + .expect("Couldn't compute merkle root") + }; + + if tx_merkle_root != l2_block.tx_merkle_root() { + return Err(StateTransitionError::SoftConfirmationError( + SoftConfirmationError::InvalidateTxMerkleRoot, + )); + } Ok(()) } diff --git a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/stf_blueprint.rs b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/stf_blueprint.rs index 017b815fb..fb91cd09c 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/stf_blueprint.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/stf_blueprint.rs @@ -1,14 +1,11 @@ use std::marker::PhantomData; -use borsh::BorshDeserialize; use sov_modules_api::hooks::HookSoftConfirmationInfo; use sov_modules_api::transaction::Transaction; -use sov_modules_api::{native_debug, native_error, Context, DaSpec, SpecId, WorkingSet}; -use sov_rollup_interface::soft_confirmation::SignedSoftConfirmation; +use sov_modules_api::{native_debug, native_error, Context, DaSpec, WorkingSet}; use sov_rollup_interface::stf::{ SoftConfirmationError, SoftConfirmationHookError, StateTransitionError, StateTransitionFunction, }; -use sov_rollup_interface::zk::StorageRootHash; #[cfg(feature = "native")] use tracing::instrument; @@ -55,29 +52,13 @@ where #[cfg_attr(feature = "native", instrument(level = "trace", skip_all))] pub fn apply_sov_txs_inner( &mut self, - soft_confirmation_info: HookSoftConfirmationInfo, - txs: &[Vec], - txs_new: &[>::Transaction], + soft_confirmation_info: &HookSoftConfirmationInfo, + txs: &[>::Transaction], sc_workspace: &mut WorkingSet, ) -> Result<(), StateTransitionError> { - if soft_confirmation_info.current_spec >= SpecId::Kumquat { - for tx in txs_new { - self.apply_sov_tx_inner(&soft_confirmation_info, tx, sc_workspace)?; - } - } else { - for raw_tx in txs { - // Stateless verification of transaction, such as signature check - let mut reader = std::io::Cursor::new(raw_tx); - let tx = Transaction::::deserialize_reader(&mut reader).map_err(|_| { - StateTransitionError::SoftConfirmationError( - SoftConfirmationError::NonSerializableSovTx, - ) - })?; - - self.apply_sov_tx_inner(&soft_confirmation_info, &tx, sc_workspace)?; - } - }; - + for tx in txs { + self.apply_sov_tx_inner(soft_confirmation_info, tx, sc_workspace)?; + } Ok(()) } @@ -149,16 +130,9 @@ where #[cfg_attr(feature = "native", instrument(level = "trace", skip_all))] pub fn end_soft_confirmation_inner( &mut self, - current_spec: SpecId, - pre_state_root: StorageRootHash, - soft_confirmation: &mut SignedSoftConfirmation< - >::Transaction, - >, + hook_soft_confirmation_info: HookSoftConfirmationInfo, working_set: &mut WorkingSet, ) -> Result<(), SoftConfirmationHookError> { - let hook_soft_confirmation_info = - HookSoftConfirmationInfo::new(soft_confirmation, pre_state_root, current_spec); - if let Err(e) = self .runtime .end_soft_confirmation_hook(hook_soft_confirmation_info, working_set) diff --git a/crates/sovereign-sdk/rollup-interface/src/node/rpc/mod.rs b/crates/sovereign-sdk/rollup-interface/src/node/rpc/mod.rs index 3d8d38479..47f4645b8 100644 --- a/crates/sovereign-sdk/rollup-interface/src/node/rpc/mod.rs +++ b/crates/sovereign-sdk/rollup-interface/src/node/rpc/mod.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::da::SequencerCommitment; use crate::mmr::MMRGuest; -use crate::soft_confirmation::SignedSoftConfirmation; +use crate::soft_confirmation::{L2Block, SignedSoftConfirmationHeader, SoftConfirmationHeader}; use crate::zk::batch_proof::output::CumulativeStateDiff; use crate::zk::light_client_proof::output::BatchProofInfo; @@ -68,7 +68,7 @@ pub struct SoftConfirmationResponse { pub txs: Option>, /// State root of the soft confirmation. #[serde(with = "hex::serde")] - pub state_root: Vec, + pub state_root: [u8; 32], /// Signature of the batch #[serde(with = "hex::serde")] pub soft_confirmation_signature: Vec, @@ -81,11 +81,13 @@ pub struct SoftConfirmationResponse { pub l1_fee_rate: u128, /// Sequencer's block timestamp. pub timestamp: u64, + /// Tx merkle root. + pub tx_merkle_root: [u8; 32], } -impl<'txs, Tx> TryFrom for SignedSoftConfirmation<'txs, Tx> +impl<'txs, Tx> TryFrom for L2Block<'txs, Tx> where - Tx: Clone + BorshDeserialize, + Tx: Clone + BorshDeserialize + BorshSerialize, { type Error = borsh::io::Error; fn try_from(val: SoftConfirmationResponse) -> Result { @@ -98,25 +100,27 @@ where borsh::from_slice::(body) }) .collect::, Self::Error>>()?; - let res = SignedSoftConfirmation::new( + + let header = SoftConfirmationHeader::new( val.l2_height, - val.hash, - val.prev_hash, val.da_slot_height, val.da_slot_hash, val.da_slot_txs_commitment, + val.prev_hash, + val.state_root, val.l1_fee_rate, - val.txs - .unwrap_or_default() - .into_iter() - .map(|tx| tx.tx) - .collect(), - parsed_txs.into(), + val.tx_merkle_root, val.deposit_data.into_iter().map(|tx| tx.tx).collect(), + val.timestamp, + ); + let signed_header = SignedSoftConfirmationHeader::new( + header, + val.hash, val.soft_confirmation_signature, val.pub_key, - val.timestamp, ); + + let res = L2Block::new(signed_header, parsed_txs.into()); Ok(res) } } diff --git a/crates/sovereign-sdk/rollup-interface/src/spec.rs b/crates/sovereign-sdk/rollup-interface/src/spec.rs index bb1e1e83a..d5ba375fe 100644 --- a/crates/sovereign-sdk/rollup-interface/src/spec.rs +++ b/crates/sovereign-sdk/rollup-interface/src/spec.rs @@ -46,7 +46,6 @@ impl SpecId { match n { 0 => Some(SpecId::Genesis), 1 => Some(SpecId::Kumquat), - #[cfg(feature = "testing")] 2 => Some(SpecId::Fork2), #[cfg(feature = "testing")] 3 => Some(SpecId::Fork3), diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/soft_confirmation.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/soft_confirmation.rs index 44c63b34c..d3de7beba 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/soft_confirmation.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/soft_confirmation.rs @@ -7,6 +7,201 @@ use borsh::{BorshDeserialize, BorshSerialize}; use digest::{Digest, Output}; use serde::{Deserialize, Serialize}; +/// Soft confirmation header +#[derive(PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug)] +pub struct SoftConfirmationHeader { + l2_height: u64, + da_slot_height: u64, + da_slot_hash: [u8; 32], + da_slot_txs_commitment: [u8; 32], + prev_hash: [u8; 32], + state_root: [u8; 32], + l1_fee_rate: u128, + tx_merkle_root: [u8; 32], + deposit_data: Vec>, + timestamp: u64, +} + +impl SoftConfirmationHeader { + #[allow(clippy::too_many_arguments)] + /// New SoftConfirmationHeader + pub fn new( + l2_height: u64, + da_slot_height: u64, + da_slot_hash: [u8; 32], + da_slot_txs_commitment: [u8; 32], + prev_hash: [u8; 32], + state_root: [u8; 32], + l1_fee_rate: u128, + tx_merkle_root: [u8; 32], + deposit_data: Vec>, + timestamp: u64, + ) -> Self { + Self { + l2_height, + da_slot_height, + da_slot_hash, + da_slot_txs_commitment, + prev_hash, + state_root, + l1_fee_rate, + tx_merkle_root, + deposit_data, + timestamp, + } + } + + /// Compute soft confirmation header digest + pub fn compute_digest(&self) -> Output { + let mut hasher = D::new(); + hasher.update(self.l2_height.to_be_bytes()); + hasher.update(self.da_slot_height.to_be_bytes()); + hasher.update(self.da_slot_hash); + hasher.update(self.da_slot_txs_commitment); + hasher.update(self.prev_hash); + hasher.update(self.state_root); + hasher.update(self.l1_fee_rate.to_be_bytes()); + hasher.update(self.tx_merkle_root); + hasher.update(self.deposit_data.concat()); + hasher.update(self.timestamp.to_be_bytes()); + hasher.finalize() + } +} + +/// Signed L2 header +#[derive(PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug)] +pub struct SignedSoftConfirmationHeader { + /// L2 header + pub inner: SoftConfirmationHeader, + /// Header hash + pub hash: [u8; 32], + /// Header signature + pub signature: Vec, + /// Sequencer pub key + pub pub_key: Vec, +} + +impl SignedSoftConfirmationHeader { + /// Crate new L2Block from header, hash and signature + pub fn new( + header: SoftConfirmationHeader, + hash: [u8; 32], + signature: Vec, + pub_key: Vec, + ) -> Self { + Self { + inner: header, + hash, + signature, + pub_key, + } + } +} + +/// Signed L2 block +#[derive(PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug)] +pub struct L2Block<'txs, Tx: Clone + BorshSerialize> { + /// Header + pub header: SignedSoftConfirmationHeader, + /// Txs of signed batch + pub txs: Cow<'txs, [Tx]>, +} + +impl<'txs, Tx: Clone + BorshSerialize> L2Block<'txs, Tx> { + /// New L2Block from headers and txs + pub fn new(header: SignedSoftConfirmationHeader, txs: Cow<'txs, [Tx]>) -> Self { + Self { header, txs } + } + + /// L2 block height + pub fn l2_height(&self) -> u64 { + self.header.inner.l2_height + } + + /// Hash of the signed batch + pub fn hash(&self) -> [u8; 32] { + self.header.hash + } + + /// Hash of the previous signed batch + pub fn prev_hash(&self) -> [u8; 32] { + self.header.inner.prev_hash + } + + /// DA block this soft confirmation was given for + pub fn da_slot_height(&self) -> u64 { + self.header.inner.da_slot_height + } + + /// DA block to build on + pub fn da_slot_hash(&self) -> [u8; 32] { + self.header.inner.da_slot_hash + } + + /// DA block transactions commitment + pub fn da_slot_txs_commitment(&self) -> [u8; 32] { + self.header.inner.da_slot_txs_commitment + } + + /// Public key of signer + pub fn sequencer_pub_key(&self) -> &[u8] { + self.header.pub_key.as_ref() + } + + /// Deposit data + pub fn deposit_data(&self) -> &[Vec] { + self.header.inner.deposit_data.as_slice() + } + + /// Signature of the sequencer + pub fn signature(&self) -> &[u8] { + self.header.signature.as_slice() + } + + /// L1 fee rate + pub fn l1_fee_rate(&self) -> u128 { + self.header.inner.l1_fee_rate + } + + /// Public key of sequencer + pub fn pub_key(&self) -> &[u8] { + self.header.pub_key.as_slice() + } + + /// Sets l1 fee rate + pub fn set_l1_fee_rate(&mut self, l1_fee_rate: u128) { + self.header.inner.l1_fee_rate = l1_fee_rate; + } + + /// Sets da slot hash + pub fn set_da_slot_hash(&mut self, da_slot_hash: [u8; 32]) { + self.header.inner.da_slot_hash = da_slot_hash; + } + + /// Sequencer block timestamp + pub fn timestamp(&self) -> u64 { + self.header.inner.timestamp + } + /// Tx merkle root + pub fn tx_merkle_root(&self) -> [u8; 32] { + self.header.inner.tx_merkle_root + } + + /// state root + pub fn state_root(&self) -> [u8; 32] { + self.header.inner.state_root + } + + /// Expensive + /// compute Borsh serialized txs + fn compute_blobs(&self) -> Vec> { + self.txs + .iter() + .map(|tx| borsh::to_vec(tx).expect("Tx serialization shouldn't fail")) + .collect() + } +} + /// Contains raw transactions and information about the soft confirmation block #[derive(Debug, PartialEq, BorshSerialize, Clone)] pub struct UnsignedSoftConfirmation<'txs, Tx> { @@ -14,23 +209,41 @@ pub struct UnsignedSoftConfirmation<'txs, Tx> { da_slot_height: u64, da_slot_hash: [u8; 32], da_slot_txs_commitment: [u8; 32], - blobs: &'txs [Vec], + blobs: Vec>, txs: &'txs [Tx], deposit_data: Vec>, l1_fee_rate: u128, timestamp: u64, } +impl<'txs, Tx: BorshSerialize> From<(&SoftConfirmationHeader, Vec>, &'txs [Tx])> + for UnsignedSoftConfirmation<'txs, Tx> +{ + fn from((header, blobs, txs): (&SoftConfirmationHeader, Vec>, &'txs [Tx])) -> Self { + UnsignedSoftConfirmation::new( + header.l2_height, + header.da_slot_height, + header.da_slot_hash, + header.da_slot_txs_commitment, + blobs, + txs, + header.deposit_data.clone(), + header.l1_fee_rate, + header.timestamp, + ) + } +} + /// Old version of UnsignedSoftConfirmation /// Used for backwards compatibility /// Always use ```UnsignedSoftConfirmation``` instead #[derive(BorshSerialize)] -pub struct UnsignedSoftConfirmationV1<'txs> { +pub struct UnsignedSoftConfirmationV1 { l2_height: u64, da_slot_height: u64, da_slot_hash: [u8; 32], da_slot_txs_commitment: [u8; 32], - blobs: &'txs [Vec], + blobs: Vec>, deposit_data: Vec>, l1_fee_rate: u128, timestamp: u64, @@ -44,7 +257,7 @@ impl<'txs, Tx: BorshSerialize> UnsignedSoftConfirmation<'txs, Tx> { da_slot_height: u64, da_slot_hash: [u8; 32], da_slot_txs_commitment: [u8; 32], - blobs: &'txs [Vec], + blobs: Vec>, txs: &'txs [Tx], deposit_data: Vec>, l1_fee_rate: u128, @@ -62,42 +275,6 @@ impl<'txs, Tx: BorshSerialize> UnsignedSoftConfirmation<'txs, Tx> { timestamp, } } - /// L2 block height - pub fn l2_height(&self) -> u64 { - self.l2_height - } - /// DA block to build on - pub fn da_slot_height(&self) -> u64 { - self.da_slot_height - } - /// DA block hash - pub fn da_slot_hash(&self) -> [u8; 32] { - self.da_slot_hash - } - /// DA block transactions commitment - pub fn da_slot_txs_commitment(&self) -> [u8; 32] { - self.da_slot_txs_commitment - } - /// Raw blobs of transactions. - pub fn blobs(&self) -> &[Vec] { - self.blobs - } - /// Transactions. - pub fn txs(&self) -> &[Tx] { - self.txs - } - /// Deposit data from L1 chain - pub fn deposit_data(&self) -> Vec> { - self.deposit_data.clone() - } - /// Base layer fee rate sats/wei etc. per byte. - pub fn l1_fee_rate(&self) -> u128 { - self.l1_fee_rate - } - /// Sequencer block timestamp - pub fn timestamp(&self) -> u64 { - self.timestamp - } /// Compute digest for the whole UnsignedSoftConfirmation struct pub fn compute_digest(&self) -> Output { let mut hasher = D::new(); @@ -105,7 +282,7 @@ impl<'txs, Tx: BorshSerialize> UnsignedSoftConfirmation<'txs, Tx> { hasher.update(self.da_slot_height.to_be_bytes()); hasher.update(self.da_slot_hash); hasher.update(self.da_slot_txs_commitment); - for tx in self.blobs { + for tx in &self.blobs { hasher.update(tx); } for deposit in &self.deposit_data { @@ -117,10 +294,45 @@ impl<'txs, Tx: BorshSerialize> UnsignedSoftConfirmation<'txs, Tx> { } } +impl<'txs, Tx: Clone + BorshSerialize> From<&'txs L2Block<'_, Tx>> + for UnsignedSoftConfirmation<'txs, Tx> +{ + fn from(block: &'txs L2Block<'_, Tx>) -> Self { + let header = &block.header.inner; + Self { + l2_height: header.l2_height, + da_slot_height: header.da_slot_height, + da_slot_hash: header.da_slot_hash, + da_slot_txs_commitment: header.da_slot_txs_commitment, + blobs: block.compute_blobs(), + txs: &block.txs, + deposit_data: header.deposit_data.clone(), + l1_fee_rate: header.l1_fee_rate, + timestamp: header.timestamp, + } + } +} + +impl<'txs, Tx: Clone + BorshSerialize> From<&'txs L2Block<'_, Tx>> for UnsignedSoftConfirmationV1 { + fn from(block: &'txs L2Block<'_, Tx>) -> Self { + let header = &block.header.inner; + Self { + l2_height: header.l2_height, + da_slot_height: header.da_slot_height, + da_slot_hash: header.da_slot_hash, + da_slot_txs_commitment: header.da_slot_txs_commitment, + blobs: block.compute_blobs(), + deposit_data: header.deposit_data.clone(), + l1_fee_rate: header.l1_fee_rate, + timestamp: header.timestamp, + } + } +} + impl<'txs, Tx: BorshSerialize> From> - for UnsignedSoftConfirmationV1<'txs> + for UnsignedSoftConfirmationV1 { - fn from(value: UnsignedSoftConfirmation<'txs, Tx>) -> Self { + fn from(value: UnsignedSoftConfirmation) -> Self { UnsignedSoftConfirmationV1 { l2_height: value.l2_height, da_slot_height: value.da_slot_height, @@ -134,7 +346,7 @@ impl<'txs, Tx: BorshSerialize> From> } } -impl<'txs> UnsignedSoftConfirmationV1<'txs> { +impl UnsignedSoftConfirmationV1 { /// Pre fork1 version of compute_digest // TODO: Remove derive(BorshSerialize) for UnsignedSoftConfirmation // when removing this fn @@ -148,7 +360,7 @@ impl<'txs> UnsignedSoftConfirmationV1<'txs> { /// Signed version of the `UnsignedSoftConfirmation` /// Contains the signature and public key of the sequencer #[derive(PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize)] -pub struct SignedSoftConfirmation<'txs, Tx: Clone> { +pub struct SignedSoftConfirmation<'txs, Tx: Clone + BorshSerialize> { l2_height: u64, hash: [u8; 32], prev_hash: [u8; 32], @@ -164,7 +376,7 @@ pub struct SignedSoftConfirmation<'txs, Tx: Clone> { timestamp: u64, } -impl<'txs, Tx: Clone> SignedSoftConfirmation<'txs, Tx> { +impl<'txs, Tx: Clone + BorshSerialize> SignedSoftConfirmation<'txs, Tx> { /// Creates a signed soft confirmation batch #[allow(clippy::too_many_arguments)] pub fn new( @@ -198,86 +410,6 @@ impl<'txs, Tx: Clone> SignedSoftConfirmation<'txs, Tx> { timestamp, } } - - /// L2 block height - pub fn l2_height(&self) -> u64 { - self.l2_height - } - - /// Hash of the signed batch - pub fn hash(&self) -> [u8; 32] { - self.hash - } - - /// Hash of the previous signed batch - pub fn prev_hash(&self) -> [u8; 32] { - self.prev_hash - } - - /// DA block this soft confirmation was given for - pub fn da_slot_height(&self) -> u64 { - self.da_slot_height - } - - /// DA block to build on - pub fn da_slot_hash(&self) -> [u8; 32] { - self.da_slot_hash - } - - /// DA block transactions commitment - pub fn da_slot_txs_commitment(&self) -> [u8; 32] { - self.da_slot_txs_commitment - } - - /// Public key of signer - pub fn sequencer_pub_key(&self) -> &[u8] { - self.pub_key.as_ref() - } - - /// Raw blob of txs of signed batch - pub fn blobs(&self) -> &[Vec] { - &self.blobs - } - - /// Txs of signed batch - pub fn txs(&self) -> &[Tx] { - &self.txs - } - - /// Deposit data - pub fn deposit_data(&self) -> &[Vec] { - self.deposit_data.as_slice() - } - - /// Signature of the sequencer - pub fn signature(&self) -> &[u8] { - self.signature.as_slice() - } - - /// L1 fee rate - pub fn l1_fee_rate(&self) -> u128 { - self.l1_fee_rate - } - - /// Public key of sequencer - pub fn pub_key(&self) -> &[u8] { - self.pub_key.as_slice() - } - - /// Sets l1 fee rate - pub fn set_l1_fee_rate(&mut self, l1_fee_rate: u128) { - self.l1_fee_rate = l1_fee_rate; - } - - /// Sets da slot hash - pub fn set_da_slot_hash(&mut self, da_slot_hash: [u8; 32]) { - self.da_slot_hash = da_slot_hash; - } - - /// Sequencer block timestamp - pub fn timestamp(&self) -> u64 { - self.timestamp - } } /// Signed version of the `UnsignedSoftConfirmation` used in Genesis @@ -298,7 +430,9 @@ pub struct SignedSoftConfirmationV1 { timestamp: u64, } -impl<'txs, Tx: Clone> From> for SignedSoftConfirmationV1 { +impl<'txs, Tx: Clone + BorshSerialize> From> + for SignedSoftConfirmationV1 +{ fn from(input: SignedSoftConfirmation<'txs, Tx>) -> Self { SignedSoftConfirmationV1 { l2_height: input.l2_height, @@ -316,3 +450,42 @@ impl<'txs, Tx: Clone> From> for SignedSoftConfi } } } + +impl<'txs, Tx: Clone + BorshSerialize> From> for SignedSoftConfirmation<'txs, Tx> { + fn from(input: L2Block<'_, Tx>) -> Self { + SignedSoftConfirmation { + l2_height: input.l2_height(), + hash: input.hash(), + prev_hash: input.prev_hash(), + da_slot_height: input.da_slot_height(), + da_slot_hash: input.da_slot_hash(), + da_slot_txs_commitment: input.da_slot_txs_commitment(), + l1_fee_rate: input.l1_fee_rate(), + blobs: Cow::Owned(input.compute_blobs().to_vec()), + txs: Cow::Owned(input.txs.to_vec()), + signature: input.signature().to_vec(), + deposit_data: input.deposit_data().to_vec(), + pub_key: input.pub_key().to_vec(), + timestamp: input.timestamp(), + } + } +} + +impl From> for SignedSoftConfirmationV1 { + fn from(input: L2Block<'_, Tx>) -> Self { + SignedSoftConfirmationV1 { + l2_height: input.l2_height(), + hash: input.hash(), + prev_hash: input.prev_hash(), + da_slot_height: input.da_slot_height(), + da_slot_hash: input.da_slot_hash(), + da_slot_txs_commitment: input.da_slot_txs_commitment(), + l1_fee_rate: input.l1_fee_rate(), + txs: input.compute_blobs().to_vec(), + signature: input.signature().to_vec(), + deposit_data: input.deposit_data().to_vec(), + pub_key: input.pub_key().to_vec(), + timestamp: input.timestamp(), + } + } +} diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs index 7f9d33286..c36e518a4 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs @@ -8,12 +8,12 @@ use std::collections::VecDeque; use borsh::{BorshDeserialize, BorshSerialize}; use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use super::zk::{StorageRootHash, ZkvmGuest}; use crate::da::DaSpec; use crate::fork::Fork; -use crate::soft_confirmation::SignedSoftConfirmation; +use crate::soft_confirmation::L2Block; use crate::spec::SpecId; use crate::zk::batch_proof::output::CumulativeStateDiff; @@ -54,35 +54,6 @@ pub struct ApplySequencerCommitmentsOutput { pub final_soft_confirmation_hash: [u8; 32], } -/// A receipt for a soft confirmation of transactions. These receipts are stored in the rollup's database -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SoftConfirmationReceipt { - /// L2 block height - pub l2_height: u64, - /// DA layer block number - pub da_slot_height: u64, - /// DA layer block hash - pub da_slot_hash: ::SlotHash, - /// DA layer transactions commitment - pub da_slot_txs_commitment: ::SlotHash, - /// The canonical hash of this batch - pub hash: [u8; 32], - /// The canonical hash of the previous batch - pub prev_hash: [u8; 32], - /// The receipts of all the transactions in this batch. - pub tx_hashes: Vec<[u8; 32]>, - /// Soft confirmation signature computed from borsh serialization of da_slot_height, da_slot_hash, pre_state_root, txs - pub soft_confirmation_signature: Vec, - /// Sequencer public key - pub pub_key: Vec, - /// Deposit data from the L1 chain - pub deposit_data: Vec>, - /// Base layer fee rate sats/wei etc. per byte. - pub l1_fee_rate: u128, - /// Sequencer's block timestamp - pub timestamp: u64, -} - /// A diff of the state, represented as a list of key-value pairs. pub type StateDiff = Vec<(Vec, Option>)>; @@ -194,7 +165,7 @@ pub trait StateTransitionFunction { state_witness: Self::Witness, offchain_witness: Self::Witness, slot_header: &Da::BlockHeader, - soft_confirmation: &mut SignedSoftConfirmation, + soft_confirmation: &mut L2Block, ) -> Result, StateTransitionError>; /// Runs a vector of Soft Confirmations @@ -237,6 +208,8 @@ pub enum SoftConfirmationError { InvalidSovTxSignature, /// The soft confirmation includes a sov-tx that can not be runtime decoded SovTxCantBeRuntimeDecoded, + /// The soft confirmation includes an invalid tx merkle root + InvalidateTxMerkleRoot, /// Any other error that can occur during the application of a soft confirmation /// These can come from runtime hooks etc. Other(String), @@ -329,6 +302,9 @@ impl std::fmt::Display for SoftConfirmationError { SoftConfirmationError::SovTxCantBeRuntimeDecoded => { write!(f, "Sov tx can't be runtime decoded") } + SoftConfirmationError::InvalidateTxMerkleRoot => { + write!(f, "Invalid tx merkle root") + } } } } diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/mod.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/mod.rs index 57666a8e2..5dc3a960a 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/mod.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/mod.rs @@ -7,7 +7,7 @@ use v2::{BatchProofCircuitInputV2Part1, BatchProofCircuitInputV2Part2}; use v3::{BatchProofCircuitInputV3Part1, BatchProofCircuitInputV3Part2}; use crate::da::DaSpec; -use crate::soft_confirmation::SignedSoftConfirmation; +use crate::soft_confirmation::L2Block; use crate::zk::StorageRootHash; /// Genesis input module @@ -23,7 +23,7 @@ pub mod v3; // StateTransitionFunction, DA, and Zkvm traits. /// Data required to verify a state transition. /// This is more like a glue type to create V1/V2 batch proof circuit inputs later in the program -pub struct BatchProofCircuitInput<'txs, Witness, Da: DaSpec, Tx: Clone> { +pub struct BatchProofCircuitInput<'txs, Witness, Da: DaSpec, Tx: Clone + BorshSerialize> { /// The state root before the state transition pub initial_state_root: StorageRootHash, /// The state root after the state transition @@ -40,12 +40,12 @@ pub struct BatchProofCircuitInput<'txs, Witness, Da: DaSpec, Tx: Clone> { pub completeness_proof: Da::CompletenessProof, /// Pre-proven commitments L2 ranges which also exist in the current L1 `da_data`. pub preproven_commitments: Vec, - /// The soft confirmations that are inside the sequencer commitments. - pub soft_confirmations: VecDeque>>, + /// The L2 blocks that are inside the sequencer commitments. + pub l2_blocks: VecDeque>>, /// Corresponding witness for the soft confirmations. pub state_transition_witnesses: VecDeque>, - /// DA block headers the soft confirmations was constructed on. - pub da_block_headers_of_soft_confirmations: VecDeque>, + /// DA block headers the L2 block was constructed on. + pub da_block_headers_of_l2_blocks: VecDeque>, /// Sequencer soft confirmation public key. /// **DO NOT USE THIS FIELD IN POST FORK1 GUEST** pub sequencer_public_key: Vec, @@ -60,7 +60,7 @@ pub struct BatchProofCircuitInput<'txs, Witness, Da: DaSpec, Tx: Clone> { impl<'txs, Witness, Da, Tx> BatchProofCircuitInput<'txs, Witness, Da, Tx> where Da: DaSpec, - Tx: Clone, + Tx: Clone + BorshSerialize, Witness: Serialize + DeserializeOwned, { /// Into Kumquat expected inputs @@ -70,14 +70,11 @@ where BatchProofCircuitInputV2Part1, BatchProofCircuitInputV2Part2<'txs, Witness, Tx>, ) { - assert_eq!( - self.soft_confirmations.len(), - self.state_transition_witnesses.len() - ); - let mut x = VecDeque::with_capacity(self.soft_confirmations.len()); + assert_eq!(self.l2_blocks.len(), self.state_transition_witnesses.len()); + let mut x = VecDeque::with_capacity(self.l2_blocks.len()); for (confirmations, witnesses) in self - .soft_confirmations + .l2_blocks .into_iter() .zip(self.state_transition_witnesses) { @@ -87,7 +84,7 @@ where .into_iter() .zip(witnesses) .map(|(confirmation, (state_witness, offchain_witness))| { - (confirmation, state_witness, offchain_witness) + (confirmation.into(), state_witness, offchain_witness) }) .collect(); @@ -104,7 +101,7 @@ where inclusion_proof: self.inclusion_proof, completeness_proof: self.completeness_proof, preproven_commitments: self.preproven_commitments, - da_block_headers_of_soft_confirmations: self.da_block_headers_of_soft_confirmations, + da_block_headers_of_l2_blocks: self.da_block_headers_of_l2_blocks, sequencer_commitments_range: self.sequencer_commitments_range, }, BatchProofCircuitInputV2Part2(x), @@ -118,14 +115,11 @@ where BatchProofCircuitInputV3Part1, BatchProofCircuitInputV3Part2<'txs, Witness, Tx>, ) { - assert_eq!( - self.soft_confirmations.len(), - self.state_transition_witnesses.len() - ); - let mut x = VecDeque::with_capacity(self.soft_confirmations.len()); + assert_eq!(self.l2_blocks.len(), self.state_transition_witnesses.len()); + let mut x = VecDeque::with_capacity(self.l2_blocks.len()); for (confirmations, witnesses) in self - .soft_confirmations + .l2_blocks .into_iter() .zip(self.state_transition_witnesses) { @@ -151,7 +145,7 @@ where inclusion_proof: self.inclusion_proof, completeness_proof: self.completeness_proof, preproven_commitments: self.preproven_commitments, - da_block_headers_of_soft_confirmations: self.da_block_headers_of_soft_confirmations, + da_block_headers_of_l2_blocks: self.da_block_headers_of_l2_blocks, sequencer_commitments_range: self.sequencer_commitments_range, }, BatchProofCircuitInputV3Part2(x), diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v1.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v1.rs index 2bad53acc..3052d5b92 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v1.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v1.rs @@ -34,8 +34,8 @@ pub struct BatchProofCircuitInputV1 { pub soft_confirmations: VecDeque>, /// Corresponding witness for the soft confirmations. pub state_transition_witnesses: VecDeque>, - /// DA block headers the soft confirmations was constructed on. - pub da_block_headers_of_soft_confirmations: VecDeque>, + /// DA block headers the L2 blocks were constructed on. + pub da_block_headers_of_l2_blocks: VecDeque>, /// Sequencer soft confirmation public key. pub sequencer_public_key: Vec, /// Sequencer DA public_key: Vec, @@ -74,9 +74,8 @@ impl BorshSerialize BorshSerialize::serialize(&self.state_transition_witnesses, writer)?; // for every Da::BlockHeader we serialize it and remove last 32 bytes - writer - .write_all(&(self.da_block_headers_of_soft_confirmations.len() as u32).to_le_bytes())?; - for header_vec in &self.da_block_headers_of_soft_confirmations { + writer.write_all(&(self.da_block_headers_of_l2_blocks.len() as u32).to_le_bytes())?; + for header_vec in &self.da_block_headers_of_l2_blocks { writer.write_all(&(header_vec.len() as u32).to_le_bytes())?; for header in header_vec { let original = borsh::to_vec(header)?; @@ -96,7 +95,7 @@ impl<'txs, Witness, Da, Tx> From> for BatchProofCircuitInputV1 where Da: DaSpec, - Tx: Clone, + Tx: Clone + BorshSerialize, Witness: Serialize + DeserializeOwned, { fn from(input: BatchProofCircuitInput<'txs, Witness, Da, Tx>) -> Self { @@ -110,7 +109,7 @@ where completeness_proof: input.completeness_proof, preproven_commitments: input.preproven_commitments, soft_confirmations: input - .soft_confirmations + .l2_blocks .into_iter() .map(|confirmations| { confirmations @@ -124,7 +123,7 @@ where .into_iter() .map(|witnesses| witnesses.into_iter().map(|(witness, _)| witness).collect()) .collect(), - da_block_headers_of_soft_confirmations: input.da_block_headers_of_soft_confirmations, + da_block_headers_of_l2_blocks: input.da_block_headers_of_l2_blocks, sequencer_public_key: input.sequencer_public_key, sequencer_da_public_key: input.sequencer_da_public_key, sequencer_commitments_range: input.sequencer_commitments_range, diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v2.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v2.rs index 00700fe78..c843c0bd0 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v2.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v2.rs @@ -11,7 +11,7 @@ use crate::zk::StorageRootHash; /// Second part of the Kumquat elf input /// This is going to be read per-need basis to not go out of memory /// in the zkvm -pub struct BatchProofCircuitInputV2Part2<'txs, Witness, Tx: Clone>( +pub struct BatchProofCircuitInputV2Part2<'txs, Witness, Tx: Clone + BorshSerialize>( pub VecDeque, Witness, Witness)>>, ); @@ -36,8 +36,8 @@ pub struct BatchProofCircuitInputV2Part1 { pub completeness_proof: Da::CompletenessProof, /// Pre-proven commitments L2 ranges which also exist in the current L1 `da_data`. pub preproven_commitments: Vec, - /// DA block headers the soft confirmations was constructed on. - pub da_block_headers_of_soft_confirmations: VecDeque>, + /// DA block headers the L2 blocks were constructed on. + pub da_block_headers_of_l2_blocks: VecDeque>, /// The range of sequencer commitments that are being processed. /// The range is inclusive. pub sequencer_commitments_range: (u32, u32), diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v3.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v3.rs index 626993c73..0ee7b6ad2 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v3.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/batch_proof/input/v3.rs @@ -4,15 +4,15 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::da::DaSpec; -use crate::soft_confirmation::SignedSoftConfirmation; +use crate::soft_confirmation::L2Block; use crate::zk::StorageRootHash; #[derive(BorshDeserialize, BorshSerialize)] /// Second part of the Kumquat elf input /// This is going to be read per-need basis to not go out of memory /// in the zkvm -pub struct BatchProofCircuitInputV3Part2<'txs, Witness, Tx: Clone>( - pub VecDeque, Witness, Witness)>>, +pub struct BatchProofCircuitInputV3Part2<'txs, Witness, Tx: Clone + BorshSerialize>( + pub VecDeque, Witness, Witness)>>, ); #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize)] @@ -34,8 +34,8 @@ pub struct BatchProofCircuitInputV3Part1 { pub completeness_proof: Da::CompletenessProof, /// Pre-proven commitments L2 ranges which also exist in the current L1 `da_data`. pub preproven_commitments: Vec, - /// DA block headers the soft confirmations was constructed on. - pub da_block_headers_of_soft_confirmations: VecDeque>, + /// DA block headers the L2 blocks were constructed on. + pub da_block_headers_of_l2_blocks: VecDeque>, /// The range of sequencer commitments that are being processed. /// The range is inclusive. pub sequencer_commitments_range: (u32, u32),