From dec000dc9e2f41f0a555be755c2b05e8e5b5e54a Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Thu, 9 Jan 2025 17:22:51 +0545 Subject: [PATCH 01/22] l1tx: Use tags to parse and build l1 payloads --- crates/btcio/src/writer/builder.rs | 1 - crates/l1tx/src/envelope/parser.rs | 1 + crates/l1tx/src/filter.rs | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/btcio/src/writer/builder.rs b/crates/btcio/src/writer/builder.rs index b3c46509f..1abc69ed2 100644 --- a/crates/btcio/src/writer/builder.rs +++ b/crates/btcio/src/writer/builder.rs @@ -79,7 +79,6 @@ pub fn create_envelope_transactions( // Start creating envelope content let reveal_script = build_reveal_script(ctx.params.as_ref(), &public_key, payloads)?; - // Create spend info for tapscript let taproot_spend_info = TaprootBuilder::new() .add_leaf(0, reveal_script.clone())? diff --git a/crates/l1tx/src/envelope/parser.rs b/crates/l1tx/src/envelope/parser.rs index ac50a3a33..7e757a3f9 100644 --- a/crates/l1tx/src/envelope/parser.rs +++ b/crates/l1tx/src/envelope/parser.rs @@ -8,6 +8,7 @@ use strata_primitives::{ params::RollupParams, }; use thiserror::Error; +use tracing::*; use crate::utils::{next_bytes, next_op}; diff --git a/crates/l1tx/src/filter.rs b/crates/l1tx/src/filter.rs index 3406625a8..2efcabfd5 100644 --- a/crates/l1tx/src/filter.rs +++ b/crates/l1tx/src/filter.rs @@ -188,7 +188,6 @@ mod test { }) .collect(); let script = generate_envelope_script_test(&payloads, params).unwrap(); - // Create controlblock let mut rand_bytes = [0; 32]; OsRng.fill_bytes(&mut rand_bytes); @@ -280,6 +279,7 @@ mod test { let filter_config = create_tx_filter_config(¶ms); let deposit_config = filter_config.deposit_config.clone(); let ee_addr = vec![1u8; 20]; // Example EVM address + let params = gen_params(); let deposit_script = build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); @@ -317,6 +317,7 @@ mod test { let params = gen_params(); let filter_config = create_tx_filter_config(¶ms); let mut deposit_config = filter_config.deposit_config.clone(); + let params = gen_params(); let extra_amt = 10000; deposit_config.deposit_amount += extra_amt; let dest_addr = vec![2u8; 20]; // Example EVM address @@ -363,6 +364,7 @@ mod test { let params = gen_params(); let filter_config = create_tx_filter_config(¶ms); let deposit_config = filter_config.deposit_config.clone(); + let params = gen_params(); let irrelevant_tx = create_test_deposit_tx( Amount::from_sat(deposit_config.deposit_amount), &test_taproot_addr().address().script_pubkey(), @@ -384,6 +386,7 @@ mod test { let params = gen_params(); let filter_config = create_tx_filter_config(¶ms); let deposit_config = filter_config.deposit_config.clone(); + let params = gen_params(); let dest_addr1 = vec![3u8; 20]; let dest_addr2 = vec![4u8; 20]; From 0e03ec048a1c79dc4eebcc12132ee30cd4179486 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 21 Jan 2025 14:46:15 +0545 Subject: [PATCH 02/22] Review fixes, fix config unit test --- crates/l1tx/src/envelope/parser.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/l1tx/src/envelope/parser.rs b/crates/l1tx/src/envelope/parser.rs index 7e757a3f9..42d0c21a4 100644 --- a/crates/l1tx/src/envelope/parser.rs +++ b/crates/l1tx/src/envelope/parser.rs @@ -141,7 +141,6 @@ mod tests { let script = generate_envelope_script_test(&[envelope1.clone(), envelope2.clone()], ¶ms) .unwrap(); - let result = parse_envelope_payloads(&script, params.rollup()).unwrap(); assert_eq!(result, vec![envelope1, envelope2]); From 7f90a3801d22962882c3bdf48ca10a8e33f0576d Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Mon, 20 Jan 2025 14:58:31 +0545 Subject: [PATCH 03/22] Update L1Tx ref types to include multiple proto ops per tx --- bin/strata-client/src/extractor.rs | 36 ++++++------ crates/chaintsn/src/transition.rs | 2 +- crates/consensus-logic/src/l1_handler.rs | 18 +++--- crates/l1tx/src/filter.rs | 56 ++++++++++--------- crates/l1tx/src/messages.rs | 10 ++-- .../proof-impl/btc-blockspace/src/filter.rs | 6 +- crates/rocksdb-store/src/l1/db.rs | 2 +- .../src/block_template/block_assembly.rs | 6 +- crates/state/src/l1/tx.rs | 10 ++-- crates/state/src/l1/utils.rs | 4 +- 10 files changed, 80 insertions(+), 70 deletions(-) diff --git a/bin/strata-client/src/extractor.rs b/bin/strata-client/src/extractor.rs index c41190615..470da0bf7 100644 --- a/bin/strata-client/src/extractor.rs +++ b/bin/strata-client/src/extractor.rs @@ -111,21 +111,23 @@ pub(super) async fn extract_deposit_requests( BitcoinAddress::parse(&original_taproot_addr.to_string(), network) .expect("address generated must be valid for the network"); - if let ProtocolOperation::DepositRequest(info) = l1_tx.protocol_operation() { - let el_address = info.address.clone(); - let total_amount = Amount::from_sat(info.amt); - let take_back_leaf_hash = TapNodeHash::from_slice(&info.take_back_leaf_hash) - .expect("a 32-byte slice must be a valid hash"); - - let deposit_info = DepositInfo::new( - deposit_request_outpoint, - el_address, - total_amount, - take_back_leaf_hash, - original_taproot_addr, - ); - - return Some(deposit_info); + for op in l1_tx.protocol_ops() { + if let ProtocolOperation::DepositRequest(info) = op { + let el_address = info.address.clone(); + let total_amount = Amount::from_sat(info.amt); + let take_back_leaf_hash = TapNodeHash::from_slice(&info.take_back_leaf_hash) + .expect("a 32-byte slice must be a valid hash"); + + let deposit_info = DepositInfo::new( + deposit_request_outpoint, + el_address, + total_amount, + take_back_leaf_hash, + original_taproot_addr, + ); + + return Some(deposit_info); + } } None @@ -405,7 +407,7 @@ mod tests { // need clone here because Rust thinks this will be called twice (and // the needle would already have been moved in the second call). num_valid_duties += 1; - return L1Tx::new(proof, needle.0.clone(), needle.1.clone()); + return L1Tx::new(proof, needle.0.clone(), vec![needle.1.clone()]); } } @@ -413,7 +415,7 @@ mod tests { if valid { num_valid_duties += 1; } - L1Tx::new(proof, tx, protocol_op) + L1Tx::new(proof, tx, vec![protocol_op]) }) .collect(); diff --git a/crates/chaintsn/src/transition.rs b/crates/chaintsn/src/transition.rs index 65ab37917..573aa8990 100644 --- a/crates/chaintsn/src/transition.rs +++ b/crates/chaintsn/src/transition.rs @@ -409,7 +409,7 @@ mod tests { outpoint: ArbitraryGenerator::new().generate(), address: [0; 20].to_vec(), }); - L1Tx::new(proof, tx, protocol_op) + L1Tx::new(proof, tx, vec![protocol_op]) } else { ArbitraryGenerator::new_with_size(1 << 15).generate() }; diff --git a/crates/consensus-logic/src/l1_handler.rs b/crates/consensus-logic/src/l1_handler.rs index cad592a6e..7199186fa 100644 --- a/crates/consensus-logic/src/l1_handler.rs +++ b/crates/consensus-logic/src/l1_handler.rs @@ -19,6 +19,7 @@ use strata_state::{ }, l1::{generate_l1_tx, L1Tx}, sync_event::SyncEvent, + tx::ProtocolOperation, }; use strata_storage::L1BlockManager; use tokio::sync::mpsc; @@ -130,15 +131,14 @@ fn check_for_da_batch( ) -> Vec { let protocol_ops_txs = blockdata.protocol_ops_txs(); - let signed_checkpts = protocol_ops_txs - .iter() - .filter_map(|ops_txs| match ops_txs.proto_op() { - strata_state::tx::ProtocolOperation::Checkpoint(envelope) => Some(( - envelope, - &blockdata.block().txdata[ops_txs.index() as usize], - )), + let signed_checkpts = protocol_ops_txs.iter().flat_map(|txref| { + txref.proto_ops().iter().filter_map(|op| match op { + ProtocolOperation::Checkpoint(envelope) => { + Some((envelope, &blockdata.block().txdata[txref.index() as usize])) + } _ => None, - }); + }) + }); let sig_verified_checkpoints = signed_checkpts.filter_map(|(signed_checkpoint, tx)| { if let Some(seq_pubkey) = seq_pubkey { @@ -245,7 +245,7 @@ fn generate_l1txs(blockdata: &BlockData) -> Vec { generate_l1_tx( blockdata.block(), ops_txs.index(), - ops_txs.proto_op().clone(), + ops_txs.proto_ops().to_vec(), ) }) .collect() diff --git a/crates/l1tx/src/filter.rs b/crates/l1tx/src/filter.rs index 2efcabfd5..77b86fd30 100644 --- a/crates/l1tx/src/filter.rs +++ b/crates/l1tx/src/filter.rs @@ -24,10 +24,8 @@ pub fn filter_protocol_op_tx_refs( .txdata .iter() .enumerate() - .flat_map(|(i, tx)| { - extract_protocol_ops(tx, params, filter_config) - .into_iter() - .map(move |relevant_tx| ProtocolOpTxRef::new(i as u32, relevant_tx)) + .map(|(i, tx)| { + ProtocolOpTxRef::new(i as u32, extract_protocol_ops(tx, params, filter_config)) }) .collect() } @@ -300,15 +298,17 @@ mod test { "The relevant transaction should be the first one" ); - if let ProtocolOperation::Deposit(deposit_info) = &result[0].proto_op() { - assert_eq!(deposit_info.address, ee_addr, "EE address should match"); - assert_eq!( - deposit_info.amt, - BitcoinAmount::from_sat(deposit_config.deposit_amount), - "Deposit amount should match" - ); - } else { - panic!("Expected Deposit info"); + for op in result[0].proto_ops() { + if let ProtocolOperation::Deposit(deposit_info) = op { + assert_eq!(deposit_info.address, ee_addr, "EE address should match"); + assert_eq!( + deposit_info.amt, + BitcoinAmount::from_sat(deposit_config.deposit_amount), + "Deposit amount should match" + ); + } else { + panic!("Expected Deposit info"); + } } } @@ -345,17 +345,19 @@ mod test { "The relevant transaction should be the first one" ); - if let ProtocolOperation::DepositRequest(deposit_req_info) = &result[0].proto_op() { - assert_eq!( - deposit_req_info.address, dest_addr, - "EE address should match" - ); - assert_eq!( - deposit_req_info.take_back_leaf_hash, dummy_block, - "Control block should match" - ); - } else { - panic!("Expected DepositRequest info"); + for op in result[0].proto_ops() { + if let ProtocolOperation::DepositRequest(deposit_req_info) = op { + assert_eq!( + deposit_req_info.address, dest_addr, + "EE address should match" + ); + assert_eq!( + deposit_req_info.take_back_leaf_hash, dummy_block, + "Control block should match" + ); + } else { + panic!("Expected DepositRequest info"); + } } } @@ -422,7 +424,11 @@ mod test { "Second relevant transaction should be at index 1" ); - for (i, info) in result.iter().map(|op_txs| op_txs.proto_op()).enumerate() { + for (i, info) in result + .iter() + .flat_map(|op_txs| op_txs.proto_ops()) + .enumerate() + { if let ProtocolOperation::Deposit(deposit_info) = info { assert_eq!( deposit_info.address, diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 795b68881..5767e03ce 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -25,13 +25,13 @@ pub struct ProtocolOpTxRef { /// Index of the transaction in the block index: u32, /// The operation that is to be applied on data - proto_op: ProtocolOperation, + proto_ops: Vec, } impl ProtocolOpTxRef { /// Creates a new ProtocolOpTxRef - pub fn new(index: u32, proto_op: ProtocolOperation) -> Self { - Self { index, proto_op } + pub fn new(index: u32, proto_ops: Vec) -> Self { + Self { index, proto_ops } } /// Returns the index of the transaction @@ -40,8 +40,8 @@ impl ProtocolOpTxRef { } /// Returns a reference to the protocol operation - pub fn proto_op(&self) -> &ProtocolOperation { - &self.proto_op + pub fn proto_ops(&self) -> &[ProtocolOperation] { + &self.proto_ops } } diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index 499a22659..8e9da6f04 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -17,10 +17,10 @@ pub fn extract_relevant_info( let mut deposits = Vec::new(); let mut prev_checkpoint = None; - let relevant_txs = filter_protocol_op_tx_refs(block, rollup_params, filter_config); + let txrefs = filter_protocol_op_tx_refs(block, rollup_params, filter_config); - for tx in relevant_txs { - match tx.proto_op() { + for op in txrefs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { + match op { ProtocolOperation::Deposit(deposit_info) => { deposits.push(deposit_info.clone()); } diff --git a/crates/rocksdb-store/src/l1/db.rs b/crates/rocksdb-store/src/l1/db.rs index 626db0b72..aaf7bc8bf 100644 --- a/crates/rocksdb-store/src/l1/db.rs +++ b/crates/rocksdb-store/src/l1/db.rs @@ -209,7 +209,7 @@ mod tests { .map(|i| { let proof = L1TxProof::new(i as u32, arb.generate()); let parsed_tx: ProtocolOperation = arb.generate(); - L1Tx::new(proof, arb.generate(), parsed_tx) + L1Tx::new(proof, arb.generate(), vec![parsed_tx]) }) .collect(); let mmr: CompactMmr = arb.generate(); diff --git a/crates/sequencer/src/block_template/block_assembly.rs b/crates/sequencer/src/block_template/block_assembly.rs index 961f42738..b43933810 100644 --- a/crates/sequencer/src/block_template/block_assembly.rs +++ b/crates/sequencer/src/block_template/block_assembly.rs @@ -195,8 +195,10 @@ fn fetch_deposit_update_txs(h: u64, l1man: &L1BlockManager) -> Result, } impl L1Tx { - pub fn new(proof: L1TxProof, tx: RawBitcoinTx, protocol_operation: ProtocolOperation) -> Self { + pub fn new(proof: L1TxProof, tx: RawBitcoinTx, protocol_ops: Vec) -> Self { Self { proof, tx, - protocol_operation, + protocol_ops, } } @@ -34,8 +34,8 @@ impl L1Tx { &self.tx } - pub fn protocol_operation(&self) -> &ProtocolOperation { - &self.protocol_operation + pub fn protocol_ops(&self) -> &[ProtocolOperation] { + &self.protocol_ops } } diff --git a/crates/state/src/l1/utils.rs b/crates/state/src/l1/utils.rs index 87e2a5855..4e7b6594c 100644 --- a/crates/state/src/l1/utils.rs +++ b/crates/state/src/l1/utils.rs @@ -34,7 +34,7 @@ pub fn compute_block_hash(header: &Header) -> Buf32 { /// /// # Panics /// - If the `idx` is out of bounds for the block's transaction data. -pub fn generate_l1_tx(block: &Block, idx: u32, proto_op_data: ProtocolOperation) -> L1Tx { +pub fn generate_l1_tx(block: &Block, idx: u32, proto_ops: Vec) -> L1Tx { assert!( (idx as usize) < block.txdata.len(), "utils: tx idx out of range of block txs" @@ -43,7 +43,7 @@ pub fn generate_l1_tx(block: &Block, idx: u32, proto_op_data: ProtocolOperation) let proof = L1TxProof::generate(&block.txdata, idx); - L1Tx::new(proof, tx.clone().into(), proto_op_data) + L1Tx::new(proof, tx.clone().into(), proto_ops) } #[cfg(test)] From 12d85c05737f0711790e90869169ffcca928b3b1 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 21 Jan 2025 17:57:33 +0545 Subject: [PATCH 04/22] Add `RawProtocolOperation`, update filtering to support extra processing --- crates/btcio/src/reader/query.rs | 15 +++- crates/l1tx/src/filter.rs | 79 +++++++++++++------ crates/l1tx/src/messages.rs | 4 +- .../proof-impl/btc-blockspace/src/filter.rs | 5 +- crates/state/src/tx.rs | 27 ++++++- 5 files changed, 96 insertions(+), 34 deletions(-) diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index 693ea879b..420f03b8b 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -324,9 +324,18 @@ async fn process_block( let txs = block.txdata.len(); let params = ctx.params.clone(); - let filtered_txs = - filter_protocol_op_tx_refs(&block, ctx.params.rollup(), state.filter_config()); - let block_data = BlockData::new(height, block, filtered_txs); + let filtered_tx_refs = filter_protocol_op_tx_refs( + &block, + ctx.params.rollup(), + state.filter_config(), + &mut |_raw_op| { + // TODO: Store relevant info in `RawProtocolOp` to db. This is relevant especially for + // DA transactions where the protocol only cares for the commitment but the node needs + // to store the actual data in db + Ok(()) + }, + ); + let block_data = BlockData::new(height, block, filtered_tx_refs); let l1blkid = block_data.block().block_hash(); trace!(%height, %l1blkid, %txs, "fetched block from client"); diff --git a/crates/l1tx/src/filter.rs b/crates/l1tx/src/filter.rs index 77b86fd30..257bc2204 100644 --- a/crates/l1tx/src/filter.rs +++ b/crates/l1tx/src/filter.rs @@ -2,7 +2,7 @@ use bitcoin::{Block, Transaction}; use strata_primitives::{l1::payload::L1PayloadType, params::RollupParams}; use strata_state::{ batch::SignedBatchCheckpoint, - tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, + tx::{DepositInfo, DepositRequestInfo, ProtocolOperation, RawProtocolOperation}, }; use tracing::warn; @@ -15,35 +15,48 @@ use crate::{ /// Filter protocol operations as refs from relevant [`Transaction`]s in a block based on given /// [`TxFilterConfig`]s -pub fn filter_protocol_op_tx_refs( +pub fn filter_protocol_op_tx_refs( block: &Block, params: &RollupParams, filter_config: &TxFilterConfig, -) -> Vec { + process_raw: &mut F, +) -> Vec +where + F: FnMut(&RawProtocolOperation) -> anyhow::Result<()>, +{ block .txdata .iter() .enumerate() .map(|(i, tx)| { - ProtocolOpTxRef::new(i as u32, extract_protocol_ops(tx, params, filter_config)) + let protocol_ops = extract_protocol_ops(tx, params, filter_config, process_raw); + ProtocolOpTxRef::new(i as u32, protocol_ops) }) .collect() } /// If a [`Transaction`] is relevant based on given [`RelevantTxType`]s then we extract relevant /// info. -// TODO: make this function return multiple ops as a single tx can have multiple outpoints that's -// relevant -fn extract_protocol_ops( +fn extract_protocol_ops( tx: &Transaction, params: &RollupParams, filter_conf: &TxFilterConfig, -) -> Vec { + process_raw: &mut F, +) -> Vec +where + F: FnMut(&RawProtocolOperation) -> anyhow::Result<()>, +{ // Currently all we have are envelope txs, deposits and deposit requests parse_envelope_checkpoints(tx, params) - .map(ProtocolOperation::Checkpoint) - .chain(parse_deposits(tx, filter_conf).map(ProtocolOperation::Deposit)) - .chain(parse_deposit_requests(tx, filter_conf).map(ProtocolOperation::DepositRequest)) + .map(RawProtocolOperation::Checkpoint) + .chain(parse_deposits(tx, filter_conf).map(RawProtocolOperation::Deposit)) + .chain(parse_deposit_requests(tx, filter_conf).map(RawProtocolOperation::DepositRequest)) + .map(|raw_op| { + if process_raw(&raw_op).is_err() { + warn!(txid=%tx.compute_txid(), "Error processing raw op for transaction id"); + } + raw_op.into() + }) .collect() } @@ -114,7 +127,10 @@ mod test { l1::{payload::L1Payload, BitcoinAmount}, params::Params, }; - use strata_state::{batch::SignedBatchCheckpoint, tx::ProtocolOperation}; + use strata_state::{ + batch::SignedBatchCheckpoint, + tx::{ProtocolOperation, RawProtocolOperation}, + }; use strata_test_utils::{l2::gen_params, ArbitraryGenerator}; use super::TxFilterConfig; @@ -207,6 +223,10 @@ mod test { tx } + fn no_op(_raw: &RawProtocolOperation) -> anyhow::Result<()> { + Ok(()) + } + #[test] fn test_filter_relevant_txs_with_rollup_envelope() { // Test with valid name @@ -218,7 +238,7 @@ mod test { let tx = create_checkpoint_envelope_tx(¶ms, num_envelopes); let block = create_test_block(vec![tx]); - let ops = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config); + let ops = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); let txids: Vec = ops.iter().map(|op_refs| op_refs.index()).collect(); assert_eq!( @@ -234,7 +254,8 @@ mod test { let tx = create_checkpoint_envelope_tx(&new_params, 2); let block = create_test_block(vec![tx]); - let result = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config); + let result = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); assert!(result.is_empty(), "Should filter out invalid name"); } @@ -246,10 +267,11 @@ mod test { let block = create_test_block(vec![tx1, tx2]); let filter_config = create_tx_filter_config(¶ms); - let txids: Vec = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config) - .iter() - .map(|op_refs| op_refs.index()) - .collect(); + let txids: Vec = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op) + .iter() + .map(|op_refs| op_refs.index()) + .collect(); assert!(txids.is_empty()); // No transactions match } @@ -262,10 +284,11 @@ mod test { let tx3 = create_checkpoint_envelope_tx(¶ms, 1); let block = create_test_block(vec![tx1, tx2, tx3]); - let txids: Vec = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config) - .iter() - .map(|op_refs| op_refs.index()) - .collect(); + let txids: Vec = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op) + .iter() + .map(|op_refs| op_refs.index()) + .collect(); // First and third txs match assert_eq!(txids[0], 0); assert_eq!(txids[1], 2); @@ -289,7 +312,8 @@ mod test { let block = create_test_block(vec![tx]); - let result = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config); + let result = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); assert_eq!(result.len(), 1, "Should find one relevant transaction"); assert_eq!( @@ -336,7 +360,8 @@ mod test { let block = create_test_block(vec![tx]); - let result = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config); + let result = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); assert_eq!(result.len(), 1, "Should find one relevant transaction"); assert_eq!( @@ -375,7 +400,8 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); - let result = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config); + let result = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); assert!( result.is_empty(), @@ -410,7 +436,8 @@ mod test { let block = create_test_block(vec![tx1, tx2]); - let result = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config); + let result = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); assert_eq!(result.len(), 2, "Should find two relevant transactions"); assert_eq!( diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 5767e03ce..edb14719d 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -18,8 +18,8 @@ pub enum L1Event { GenesisVerificationState(u64, HeaderVerificationState), } -/// Core protocol specific transaction. It can be thought of as relevant transactions for the -/// Protocol +/// Core protocol specific bitcoin transaction reference. A bitcoin transaction can have multiple +/// operations relevant to protocol. This is used in the context of [`BlockData`]. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct ProtocolOpTxRef { /// Index of the transaction in the block diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index 8e9da6f04..37f70bf61 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -17,7 +17,10 @@ pub fn extract_relevant_info( let mut deposits = Vec::new(); let mut prev_checkpoint = None; - let txrefs = filter_protocol_op_tx_refs(block, rollup_params, filter_config); + // Just pass a no-op to the filter function as prover does not have to do anything with the raw + // data like storing in db. + let txrefs = + filter_protocol_op_tx_refs(block, rollup_params, filter_config, &mut |_raw_op| Ok(())); for op in txrefs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { match op { diff --git a/crates/state/src/tx.rs b/crates/state/src/tx.rs index d27414bee..b318d6127 100644 --- a/crates/state/src/tx.rs +++ b/crates/state/src/tx.rs @@ -5,7 +5,7 @@ use strata_primitives::l1::{BitcoinAmount, OutputRef}; use crate::batch::SignedBatchCheckpoint; -/// Information related to relevant transactions to be stored in L1Tx +/// Information related to relevant transactions to be stored in an `L1Tx`. #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, )] @@ -17,7 +17,30 @@ pub enum ProtocolOperation { DepositRequest(DepositRequestInfo), /// Checkpoint data Checkpoint(SignedBatchCheckpoint), - // TODO: add other kinds like Proofs and statediffs + // TODO: add other kinds like proofs and state diffs +} + +/// Similar to [`ProtocolOperation`] except that this also contains blob data which is not relevant +/// to chain. +#[allow(clippy::large_enum_variant)] +pub enum RawProtocolOperation { + /// Deposit Transaction + Deposit(DepositInfo), + /// Deposit Request info + DepositRequest(DepositRequestInfo), + /// Checkpoint data + Checkpoint(SignedBatchCheckpoint), + // TODO: add other kinds like proofs and state diffs +} + +impl From for ProtocolOperation { + fn from(val: RawProtocolOperation) -> Self { + match val { + RawProtocolOperation::DepositRequest(d) => ProtocolOperation::DepositRequest(d), + RawProtocolOperation::Deposit(d) => ProtocolOperation::Deposit(d), + RawProtocolOperation::Checkpoint(c) => ProtocolOperation::Checkpoint(c), + } + } } #[derive( From 08eb63b02ca325433c785e21cac8b647b321e991 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 21 Jan 2025 18:47:46 +0545 Subject: [PATCH 05/22] Add unit test for parsing multiple protocol ops in a tx --- crates/l1tx/src/filter.rs | 48 +++++++++++++++++++++++++++++++++++++++ crates/state/src/tx.rs | 3 +++ 2 files changed, 51 insertions(+) diff --git a/crates/l1tx/src/filter.rs b/crates/l1tx/src/filter.rs index 257bc2204..7122072c3 100644 --- a/crates/l1tx/src/filter.rs +++ b/crates/l1tx/src/filter.rs @@ -336,6 +336,54 @@ mod test { } } + #[test] + fn test_filter_multiple_ops_in_single_tx() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let ee_addr = vec![1u8; 20]; // Example EVM address + + // Create deposit utxo + let deposit_script = + build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); + + let mut tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script, + ); + + // Create envelope tx and copy its input to the above deposit tx + let num_envelopes = 1; + let envtx = create_checkpoint_envelope_tx(¶ms, num_envelopes); + tx.input.push(envtx.input[0].clone()); + + // Create a block with single tx that has multiple ops + let block = create_test_block(vec![tx]); + + let result = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + + assert_eq!(result.len(), 1, "Should find one relevant transaction"); + assert_eq!( + result[0].proto_ops().len(), + 2, + "Should find two protocol ops" + ); + + let mut dep_count = 0; + let mut ckpt_count = 0; + for op in result[0].proto_ops() { + match op { + ProtocolOperation::Deposit(_) => dep_count += 1, + ProtocolOperation::Checkpoint(_) => ckpt_count += 1, + _ => {} + } + } + assert_eq!(dep_count, 1, "should have one deposit"); + assert_eq!(ckpt_count, 1, "should have one checkpoint"); + } + #[test] fn test_filter_relevant_txs_deposit_request() { let params = gen_params(); diff --git a/crates/state/src/tx.rs b/crates/state/src/tx.rs index b318d6127..ad6cdb5b3 100644 --- a/crates/state/src/tx.rs +++ b/crates/state/src/tx.rs @@ -22,6 +22,9 @@ pub enum ProtocolOperation { /// Similar to [`ProtocolOperation`] except that this also contains blob data which is not relevant /// to chain. +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, +)] #[allow(clippy::large_enum_variant)] pub enum RawProtocolOperation { /// Deposit Transaction From 85690e326b3e190fac4fdf561aa0622f9cbbdfd7 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 21 Jan 2025 20:47:48 +0545 Subject: [PATCH 06/22] l1tx: Fix filter unit tests --- crates/l1tx/src/filter.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/l1tx/src/filter.rs b/crates/l1tx/src/filter.rs index 7122072c3..099001b83 100644 --- a/crates/l1tx/src/filter.rs +++ b/crates/l1tx/src/filter.rs @@ -32,6 +32,8 @@ where let protocol_ops = extract_protocol_ops(tx, params, filter_config, process_raw); ProtocolOpTxRef::new(i as u32, protocol_ops) }) + // Filter out tx refs which do not contain protocol ops + .filter(|txref| !txref.proto_ops().is_empty()) .collect() } @@ -238,15 +240,15 @@ mod test { let tx = create_checkpoint_envelope_tx(¶ms, num_envelopes); let block = create_test_block(vec![tx]); - let ops = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); - let txids: Vec = ops.iter().map(|op_refs| op_refs.index()).collect(); + let tx_refs = + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + assert_eq!(tx_refs.len(), 1, "There's one tx containing protocol ops"); assert_eq!( - ops.len(), - num_envelopes as usize, - "All the envelopes should be identified" + tx_refs[0].proto_ops().len(), + 2, + "Should filter relevant envelopes" ); - assert_eq!(txids[0], 0, "Should filter valid rollup name"); // Test with invalid checkpoint tag let mut new_params = params.clone(); @@ -256,7 +258,7 @@ mod test { let block = create_test_block(vec![tx]); let result = filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); - assert!(result.is_empty(), "Should filter out invalid name"); + assert!(result.is_empty(), "Should filter out irrelevant txs"); } #[test] From c97e23873dbe1145a9de666dae621522001c5985 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Wed, 22 Jan 2025 09:57:19 +0545 Subject: [PATCH 07/22] Cleanup: remove generic F in proto ops filtering, some renaming --- crates/btcio/src/reader/query.rs | 2 +- crates/l1tx/src/filter.rs | 22 ++++++++-------------- crates/l1tx/src/messages.rs | 12 ++++++------ crates/state/src/tx.rs | 2 ++ 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index 420f03b8b..9e93c47d2 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -329,7 +329,7 @@ async fn process_block( ctx.params.rollup(), state.filter_config(), &mut |_raw_op| { - // TODO: Store relevant info in `RawProtocolOp` to db. This is relevant especially for + // TODO: Store relevant info in `RawProtocolOp` to db. This is useful especially for // DA transactions where the protocol only cares for the commitment but the node needs // to store the actual data in db Ok(()) diff --git a/crates/l1tx/src/filter.rs b/crates/l1tx/src/filter.rs index 099001b83..31e2ad7ac 100644 --- a/crates/l1tx/src/filter.rs +++ b/crates/l1tx/src/filter.rs @@ -6,7 +6,7 @@ use strata_state::{ }; use tracing::warn; -use super::messages::ProtocolOpTxRef; +use super::messages::ProtocolOpsTxRef; pub use crate::filter_types::TxFilterConfig; use crate::{ deposit::{deposit_request::extract_deposit_request_info, deposit_tx::extract_deposit_info}, @@ -15,22 +15,19 @@ use crate::{ /// Filter protocol operations as refs from relevant [`Transaction`]s in a block based on given /// [`TxFilterConfig`]s -pub fn filter_protocol_op_tx_refs( +pub fn filter_protocol_op_tx_refs( block: &Block, params: &RollupParams, filter_config: &TxFilterConfig, - process_raw: &mut F, -) -> Vec -where - F: FnMut(&RawProtocolOperation) -> anyhow::Result<()>, -{ + process_raw: &mut impl FnMut(&RawProtocolOperation) -> anyhow::Result<()>, +) -> Vec { block .txdata .iter() .enumerate() .map(|(i, tx)| { let protocol_ops = extract_protocol_ops(tx, params, filter_config, process_raw); - ProtocolOpTxRef::new(i as u32, protocol_ops) + ProtocolOpsTxRef::new(i as u32, protocol_ops) }) // Filter out tx refs which do not contain protocol ops .filter(|txref| !txref.proto_ops().is_empty()) @@ -39,15 +36,12 @@ where /// If a [`Transaction`] is relevant based on given [`RelevantTxType`]s then we extract relevant /// info. -fn extract_protocol_ops( +fn extract_protocol_ops( tx: &Transaction, params: &RollupParams, filter_conf: &TxFilterConfig, - process_raw: &mut F, -) -> Vec -where - F: FnMut(&RawProtocolOperation) -> anyhow::Result<()>, -{ + process_raw: &mut impl FnMut(&RawProtocolOperation) -> anyhow::Result<()>, +) -> Vec { // Currently all we have are envelope txs, deposits and deposit requests parse_envelope_checkpoints(tx, params) .map(RawProtocolOperation::Checkpoint) diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index edb14719d..07306c81d 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -21,15 +21,15 @@ pub enum L1Event { /// Core protocol specific bitcoin transaction reference. A bitcoin transaction can have multiple /// operations relevant to protocol. This is used in the context of [`BlockData`]. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] -pub struct ProtocolOpTxRef { +pub struct ProtocolOpsTxRef { /// Index of the transaction in the block index: u32, /// The operation that is to be applied on data proto_ops: Vec, } -impl ProtocolOpTxRef { - /// Creates a new ProtocolOpTxRef +impl ProtocolOpsTxRef { + /// Creates a new [`ProtocolOpsTxRef`] pub fn new(index: u32, proto_ops: Vec) -> Self { Self { index, proto_ops } } @@ -51,11 +51,11 @@ pub struct BlockData { block_num: u64, block: Block, /// Transactions in the block that are relevant to rollup - protocol_ops_txs: Vec, + protocol_ops_txs: Vec, } impl BlockData { - pub fn new(block_num: u64, block: Block, protocol_ops_txs: Vec) -> Self { + pub fn new(block_num: u64, block: Block, protocol_ops_txs: Vec) -> Self { Self { block_num, block, @@ -71,7 +71,7 @@ impl BlockData { self.protocol_ops_txs.iter().map(|v| v.index) } - pub fn protocol_ops_txs(&self) -> &[ProtocolOpTxRef] { + pub fn protocol_ops_txs(&self) -> &[ProtocolOpsTxRef] { &self.protocol_ops_txs } diff --git a/crates/state/src/tx.rs b/crates/state/src/tx.rs index ad6cdb5b3..7e0fcc6b9 100644 --- a/crates/state/src/tx.rs +++ b/crates/state/src/tx.rs @@ -18,6 +18,7 @@ pub enum ProtocolOperation { /// Checkpoint data Checkpoint(SignedBatchCheckpoint), // TODO: add other kinds like proofs and state diffs + // DACommitment(Buf32), } /// Similar to [`ProtocolOperation`] except that this also contains blob data which is not relevant @@ -34,6 +35,7 @@ pub enum RawProtocolOperation { /// Checkpoint data Checkpoint(SignedBatchCheckpoint), // TODO: add other kinds like proofs and state diffs + // DA(Vec), } impl From for ProtocolOperation { From d8fa602b7e11b32e04b84e7ae12c0b938549beb1 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Thu, 23 Jan 2025 15:22:41 +0545 Subject: [PATCH 08/22] l1tx/filter: Add visitor trait --- crates/btcio/src/reader/mod.rs | 1 + crates/btcio/src/reader/ops_visitor.rs | 14 ++++ crates/btcio/src/reader/query.rs | 19 ++---- crates/l1tx/src/{filter.rs => filter/mod.rs} | 66 +++++++++---------- .../src/{filter_types.rs => filter/types.rs} | 0 crates/l1tx/src/filter/visitor.rs | 29 ++++++++ crates/l1tx/src/lib.rs | 1 - .../proof-impl/btc-blockspace/src/filter.rs | 10 ++- crates/state/src/tx.rs | 35 ++-------- 9 files changed, 94 insertions(+), 81 deletions(-) create mode 100644 crates/btcio/src/reader/ops_visitor.rs rename crates/l1tx/src/{filter.rs => filter/mod.rs} (93%) rename crates/l1tx/src/{filter_types.rs => filter/types.rs} (100%) create mode 100644 crates/l1tx/src/filter/visitor.rs diff --git a/crates/btcio/src/reader/mod.rs b/crates/btcio/src/reader/mod.rs index 1c81b603d..06d97b42e 100644 --- a/crates/btcio/src/reader/mod.rs +++ b/crates/btcio/src/reader/mod.rs @@ -1,2 +1,3 @@ +mod ops_visitor; pub mod query; mod state; diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/ops_visitor.rs new file mode 100644 index 000000000..b165a98aa --- /dev/null +++ b/crates/btcio/src/reader/ops_visitor.rs @@ -0,0 +1,14 @@ +use strata_l1tx::filter::visitor::OpsVisitor; +use strata_primitives::buf::Buf32; +use strata_state::tx::ProtocolOperation; + +/// Ops visitor for rollup client. +// TODO: add db manager +pub struct ClientOpsVisitor; + +impl OpsVisitor for ClientOpsVisitor { + fn visit_da(&self, _d: &[u8]) -> ProtocolOperation { + // TODO: insert in db, when we have DA + ProtocolOperation::DaCommitment(Buf32::zero()) + } +} diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index 9e93c47d2..4343a1646 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -8,8 +8,7 @@ use anyhow::bail; use bitcoin::{Block, BlockHash}; use strata_config::btcio::ReaderConfig; use strata_l1tx::{ - filter::filter_protocol_op_tx_refs, - filter_types::TxFilterConfig, + filter::{filter_protocol_op_tx_refs, TxFilterConfig}, messages::{BlockData, L1Event}, }; use strata_primitives::params::Params; @@ -22,7 +21,7 @@ use tokio::sync::mpsc; use tracing::*; use crate::{ - reader::state::ReaderState, + reader::{ops_visitor::ClientOpsVisitor, state::ReaderState}, rpc::traits::ReaderRpc, status::{apply_status_updates, L1StatusUpdate}, }; @@ -324,17 +323,9 @@ async fn process_block( let txs = block.txdata.len(); let params = ctx.params.clone(); - let filtered_tx_refs = filter_protocol_op_tx_refs( - &block, - ctx.params.rollup(), - state.filter_config(), - &mut |_raw_op| { - // TODO: Store relevant info in `RawProtocolOp` to db. This is useful especially for - // DA transactions where the protocol only cares for the commitment but the node needs - // to store the actual data in db - Ok(()) - }, - ); + let visitor = ClientOpsVisitor; + let filtered_tx_refs = + filter_protocol_op_tx_refs(&block, ctx.params.rollup(), state.filter_config(), &visitor); let block_data = BlockData::new(height, block, filtered_tx_refs); let l1blkid = block_data.block().block_hash(); trace!(%height, %l1blkid, %txs, "fetched block from client"); diff --git a/crates/l1tx/src/filter.rs b/crates/l1tx/src/filter/mod.rs similarity index 93% rename from crates/l1tx/src/filter.rs rename to crates/l1tx/src/filter/mod.rs index 31e2ad7ac..b21d71c2c 100644 --- a/crates/l1tx/src/filter.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -2,12 +2,17 @@ use bitcoin::{Block, Transaction}; use strata_primitives::{l1::payload::L1PayloadType, params::RollupParams}; use strata_state::{ batch::SignedBatchCheckpoint, - tx::{DepositInfo, DepositRequestInfo, ProtocolOperation, RawProtocolOperation}, + tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, }; use tracing::warn; +mod types; +pub mod visitor; + +pub use types::TxFilterConfig; +use visitor::OpsVisitor; + use super::messages::ProtocolOpsTxRef; -pub use crate::filter_types::TxFilterConfig; use crate::{ deposit::{deposit_request::extract_deposit_request_info, deposit_tx::extract_deposit_info}, envelope::parser::parse_envelope_payloads, @@ -15,18 +20,18 @@ use crate::{ /// Filter protocol operations as refs from relevant [`Transaction`]s in a block based on given /// [`TxFilterConfig`]s -pub fn filter_protocol_op_tx_refs( +pub fn filter_protocol_op_tx_refs( block: &Block, params: &RollupParams, filter_config: &TxFilterConfig, - process_raw: &mut impl FnMut(&RawProtocolOperation) -> anyhow::Result<()>, + ops_visitor: &V, ) -> Vec { block .txdata .iter() .enumerate() .map(|(i, tx)| { - let protocol_ops = extract_protocol_ops(tx, params, filter_config, process_raw); + let protocol_ops = extract_protocol_ops(tx, params, filter_config, ops_visitor); ProtocolOpsTxRef::new(i as u32, protocol_ops) }) // Filter out tx refs which do not contain protocol ops @@ -36,23 +41,19 @@ pub fn filter_protocol_op_tx_refs( /// If a [`Transaction`] is relevant based on given [`RelevantTxType`]s then we extract relevant /// info. -fn extract_protocol_ops( +fn extract_protocol_ops( tx: &Transaction, params: &RollupParams, filter_conf: &TxFilterConfig, - process_raw: &mut impl FnMut(&RawProtocolOperation) -> anyhow::Result<()>, + ops_visitor: &V, ) -> Vec { // Currently all we have are envelope txs, deposits and deposit requests parse_envelope_checkpoints(tx, params) - .map(RawProtocolOperation::Checkpoint) - .chain(parse_deposits(tx, filter_conf).map(RawProtocolOperation::Deposit)) - .chain(parse_deposit_requests(tx, filter_conf).map(RawProtocolOperation::DepositRequest)) - .map(|raw_op| { - if process_raw(&raw_op).is_err() { - warn!(txid=%tx.compute_txid(), "Error processing raw op for transaction id"); - } - raw_op.into() - }) + .map(|c| ops_visitor.visit_checkpoint(c)) + .chain(parse_deposits(tx, filter_conf).map(|x| ops_visitor.visit_deposit(x))) + .chain( + parse_deposit_requests(tx, filter_conf).map(|x| ops_visitor.visit_deposit_request(x)), + ) .collect() } @@ -123,13 +124,10 @@ mod test { l1::{payload::L1Payload, BitcoinAmount}, params::Params, }; - use strata_state::{ - batch::SignedBatchCheckpoint, - tx::{ProtocolOperation, RawProtocolOperation}, - }; + use strata_state::{batch::SignedBatchCheckpoint, tx::ProtocolOperation}; use strata_test_utils::{l2::gen_params, ArbitraryGenerator}; - use super::TxFilterConfig; + use super::{visitor::OpsVisitor, TxFilterConfig}; use crate::{ deposit::test_utils::{ build_test_deposit_request_script, build_test_deposit_script, create_test_deposit_tx, @@ -140,6 +138,10 @@ mod test { const OTHER_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; + struct TestVisitor; + + impl OpsVisitor for TestVisitor {} + /// Helper function to create filter config fn create_tx_filter_config(params: &Params) -> TxFilterConfig { TxFilterConfig::derive_from(params.rollup()).expect("can't get filter config") @@ -219,10 +221,6 @@ mod test { tx } - fn no_op(_raw: &RawProtocolOperation) -> anyhow::Result<()> { - Ok(()) - } - #[test] fn test_filter_relevant_txs_with_rollup_envelope() { // Test with valid name @@ -235,7 +233,7 @@ mod test { let block = create_test_block(vec![tx]); let tx_refs = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); assert_eq!(tx_refs.len(), 1, "There's one tx containing protocol ops"); assert_eq!( @@ -251,7 +249,7 @@ mod test { let tx = create_checkpoint_envelope_tx(&new_params, 2); let block = create_test_block(vec![tx]); let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); assert!(result.is_empty(), "Should filter out irrelevant txs"); } @@ -264,7 +262,7 @@ mod test { let filter_config = create_tx_filter_config(¶ms); let txids: Vec = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op) + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor) .iter() .map(|op_refs| op_refs.index()) .collect(); @@ -281,7 +279,7 @@ mod test { let block = create_test_block(vec![tx1, tx2, tx3]); let txids: Vec = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op) + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor) .iter() .map(|op_refs| op_refs.index()) .collect(); @@ -309,7 +307,7 @@ mod test { let block = create_test_block(vec![tx]); let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); assert_eq!(result.len(), 1, "Should find one relevant transaction"); assert_eq!( @@ -358,7 +356,7 @@ mod test { let block = create_test_block(vec![tx]); let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); assert_eq!(result.len(), 1, "Should find one relevant transaction"); assert_eq!( @@ -405,7 +403,7 @@ mod test { let block = create_test_block(vec![tx]); let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); assert_eq!(result.len(), 1, "Should find one relevant transaction"); assert_eq!( @@ -445,7 +443,7 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); assert!( result.is_empty(), @@ -481,7 +479,7 @@ mod test { let block = create_test_block(vec![tx1, tx2]); let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &mut no_op); + filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); assert_eq!(result.len(), 2, "Should find two relevant transactions"); assert_eq!( diff --git a/crates/l1tx/src/filter_types.rs b/crates/l1tx/src/filter/types.rs similarity index 100% rename from crates/l1tx/src/filter_types.rs rename to crates/l1tx/src/filter/types.rs diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/visitor.rs new file mode 100644 index 000000000..d50ec7488 --- /dev/null +++ b/crates/l1tx/src/filter/visitor.rs @@ -0,0 +1,29 @@ +use strata_primitives::hash; +use strata_state::{ + batch::SignedBatchCheckpoint, + tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, +}; + +pub trait OpsVisitor { + // Do stuffs with `SignedBatchCheckpoint`. + fn visit_checkpoint(&self, chkpt: SignedBatchCheckpoint) -> ProtocolOperation { + ProtocolOperation::Checkpoint(chkpt) + } + + // Do stuffs with `DepositInfo`. + fn visit_deposit(&self, d: DepositInfo) -> ProtocolOperation { + ProtocolOperation::Deposit(d) + } + + // Do stuffs with `DepositRequest`. + fn visit_deposit_request(&self, d: DepositRequestInfo) -> ProtocolOperation { + ProtocolOperation::DepositRequest(d) + } + + // Do stuffs with DA. + fn visit_da(&self, d: &[u8]) -> ProtocolOperation { + // TODO: this default implementation might change when we actually have DA + let commitment = hash::raw(d); + ProtocolOperation::DaCommitment(commitment) + } +} diff --git a/crates/l1tx/src/lib.rs b/crates/l1tx/src/lib.rs index eeb1b53a7..8eb326260 100644 --- a/crates/l1tx/src/lib.rs +++ b/crates/l1tx/src/lib.rs @@ -1,6 +1,5 @@ pub mod deposit; pub mod envelope; pub mod filter; -pub mod filter_types; pub mod messages; pub mod utils; diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index 37f70bf61..7a2c85f26 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -2,13 +2,18 @@ //! deposits, forced inclusion transactions as well as state updates use bitcoin::Block; -use strata_l1tx::filter::{filter_protocol_op_tx_refs, TxFilterConfig}; +use strata_l1tx::filter::{filter_protocol_op_tx_refs, visitor::OpsVisitor, TxFilterConfig}; use strata_primitives::{block_credential::CredRule, params::RollupParams}; use strata_state::{ batch::BatchCheckpoint, tx::{DepositInfo, ProtocolOperation}, }; +/// Ops visitor for Prover. +pub struct ProverOpsVisitor; + +impl OpsVisitor for ProverOpsVisitor {} + pub fn extract_relevant_info( block: &Block, rollup_params: &RollupParams, @@ -19,8 +24,7 @@ pub fn extract_relevant_info( // Just pass a no-op to the filter function as prover does not have to do anything with the raw // data like storing in db. - let txrefs = - filter_protocol_op_tx_refs(block, rollup_params, filter_config, &mut |_raw_op| Ok(())); + let txrefs = filter_protocol_op_tx_refs(block, rollup_params, filter_config, &ProverOpsVisitor); for op in txrefs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { match op { diff --git a/crates/state/src/tx.rs b/crates/state/src/tx.rs index 7e0fcc6b9..96a998fa1 100644 --- a/crates/state/src/tx.rs +++ b/crates/state/src/tx.rs @@ -1,7 +1,10 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::l1::{BitcoinAmount, OutputRef}; +use strata_primitives::{ + buf::Buf32, + l1::{BitcoinAmount, OutputRef}, +}; use crate::batch::SignedBatchCheckpoint; @@ -17,35 +20,9 @@ pub enum ProtocolOperation { DepositRequest(DepositRequestInfo), /// Checkpoint data Checkpoint(SignedBatchCheckpoint), + // Da Commitment + DaCommitment(Buf32), // TODO: add other kinds like proofs and state diffs - // DACommitment(Buf32), -} - -/// Similar to [`ProtocolOperation`] except that this also contains blob data which is not relevant -/// to chain. -#[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, -)] -#[allow(clippy::large_enum_variant)] -pub enum RawProtocolOperation { - /// Deposit Transaction - Deposit(DepositInfo), - /// Deposit Request info - DepositRequest(DepositRequestInfo), - /// Checkpoint data - Checkpoint(SignedBatchCheckpoint), - // TODO: add other kinds like proofs and state diffs - // DA(Vec), -} - -impl From for ProtocolOperation { - fn from(val: RawProtocolOperation) -> Self { - match val { - RawProtocolOperation::DepositRequest(d) => ProtocolOperation::DepositRequest(d), - RawProtocolOperation::Deposit(d) => ProtocolOperation::Deposit(d), - RawProtocolOperation::Checkpoint(c) => ProtocolOperation::Checkpoint(c), - } - } } #[derive( From cf3c2c7e4f2a65813df6ee52e1197f7984eae99c Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Thu, 23 Jan 2025 16:08:10 +0545 Subject: [PATCH 09/22] Rename ProtocolOpsTxRef -> ProtocolTxEntry --- crates/consensus-logic/src/l1_handler.rs | 4 ++-- crates/l1tx/src/filter/mod.rs | 6 +++--- crates/l1tx/src/messages.rs | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/consensus-logic/src/l1_handler.rs b/crates/consensus-logic/src/l1_handler.rs index 7199186fa..7a2f4df54 100644 --- a/crates/consensus-logic/src/l1_handler.rs +++ b/crates/consensus-logic/src/l1_handler.rs @@ -129,7 +129,7 @@ fn check_for_da_batch( blockdata: &BlockData, seq_pubkey: Option, ) -> Vec { - let protocol_ops_txs = blockdata.protocol_ops_txs(); + let protocol_ops_txs = blockdata.protocol_txs(); let signed_checkpts = protocol_ops_txs.iter().flat_map(|txref| { txref.proto_ops().iter().filter_map(|op| match op { @@ -239,7 +239,7 @@ fn generate_block_manifest(block: &Block, epoch: u64) -> L1BlockManifest { fn generate_l1txs(blockdata: &BlockData) -> Vec { blockdata - .protocol_ops_txs() + .protocol_txs() .iter() .map(|ops_txs| { generate_l1_tx( diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index b21d71c2c..4363c3a98 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -12,7 +12,7 @@ pub mod visitor; pub use types::TxFilterConfig; use visitor::OpsVisitor; -use super::messages::ProtocolOpsTxRef; +use super::messages::ProtocolTxEntry; use crate::{ deposit::{deposit_request::extract_deposit_request_info, deposit_tx::extract_deposit_info}, envelope::parser::parse_envelope_payloads, @@ -25,14 +25,14 @@ pub fn filter_protocol_op_tx_refs( params: &RollupParams, filter_config: &TxFilterConfig, ops_visitor: &V, -) -> Vec { +) -> Vec { block .txdata .iter() .enumerate() .map(|(i, tx)| { let protocol_ops = extract_protocol_ops(tx, params, filter_config, ops_visitor); - ProtocolOpsTxRef::new(i as u32, protocol_ops) + ProtocolTxEntry::new(i as u32, protocol_ops) }) // Filter out tx refs which do not contain protocol ops .filter(|txref| !txref.proto_ops().is_empty()) diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 07306c81d..25e57f147 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -21,15 +21,15 @@ pub enum L1Event { /// Core protocol specific bitcoin transaction reference. A bitcoin transaction can have multiple /// operations relevant to protocol. This is used in the context of [`BlockData`]. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] -pub struct ProtocolOpsTxRef { +pub struct ProtocolTxEntry { /// Index of the transaction in the block index: u32, /// The operation that is to be applied on data proto_ops: Vec, } -impl ProtocolOpsTxRef { - /// Creates a new [`ProtocolOpsTxRef`] +impl ProtocolTxEntry { + /// Creates a new [`ProtocolTxEntry`] pub fn new(index: u32, proto_ops: Vec) -> Self { Self { index, proto_ops } } @@ -50,16 +50,16 @@ impl ProtocolOpsTxRef { pub struct BlockData { block_num: u64, block: Block, - /// Transactions in the block that are relevant to rollup - protocol_ops_txs: Vec, + /// Transactions in the block that contain protocol operations + protocol_txs: Vec, } impl BlockData { - pub fn new(block_num: u64, block: Block, protocol_ops_txs: Vec) -> Self { + pub fn new(block_num: u64, block: Block, protocol_txs: Vec) -> Self { Self { block_num, block, - protocol_ops_txs, + protocol_txs, } } @@ -67,12 +67,12 @@ impl BlockData { &self.block } - pub fn protocol_ops_tx_idxs(&self) -> impl Iterator + '_ { - self.protocol_ops_txs.iter().map(|v| v.index) + pub fn protocol_tx_idxs(&self) -> impl Iterator + '_ { + self.protocol_txs.iter().map(|v| v.index) } - pub fn protocol_ops_txs(&self) -> &[ProtocolOpsTxRef] { - &self.protocol_ops_txs + pub fn protocol_txs(&self) -> &[ProtocolTxEntry] { + &self.protocol_txs } pub fn block_num(&self) -> u64 { From 0899eb7bb83ce50b5125aba3b87189a90988417a Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Mon, 27 Jan 2025 12:28:12 +0545 Subject: [PATCH 10/22] Add block and tx indexer along with ops_visitor --- Cargo.lock | 2 + crates/btcio/Cargo.toml | 1 + crates/btcio/src/reader/ops_visitor.rs | 294 +++++++++++++- crates/btcio/src/reader/query.rs | 34 +- crates/l1tx/src/envelope/parser.rs | 29 +- crates/l1tx/src/filter/mod.rs | 380 +++--------------- crates/l1tx/src/filter/types.rs | 17 +- crates/l1tx/src/filter/visitor.rs | 104 ++++- crates/proof-impl/btc-blockspace/Cargo.toml | 1 + .../proof-impl/btc-blockspace/src/filter.rs | 48 ++- 10 files changed, 539 insertions(+), 371 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f51fb561..92c4ee4d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12879,6 +12879,7 @@ dependencies = [ "bitcoin", "bytes", "corepc-node", + "digest 0.10.7", "hex", "mockall", "musig2", @@ -13258,6 +13259,7 @@ version = "0.1.0" dependencies = [ "bitcoin", "borsh", + "digest 0.10.7", "hex", "rand", "serde", diff --git a/crates/btcio/Cargo.toml b/crates/btcio/Cargo.toml index ca0266211..809cfbbf7 100644 --- a/crates/btcio/Cargo.toml +++ b/crates/btcio/Cargo.toml @@ -19,6 +19,7 @@ async-trait.workspace = true base64.workspace = true bitcoin.workspace = true bytes.workspace = true +digest.workspace = true hex.workspace = true musig2 = { workspace = true, features = ["serde"] } rand.workspace = true diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/ops_visitor.rs index b165a98aa..0af971d79 100644 --- a/crates/btcio/src/reader/ops_visitor.rs +++ b/crates/btcio/src/reader/ops_visitor.rs @@ -1,14 +1,294 @@ +use digest::Digest; +use sha2::Sha256; use strata_l1tx::filter::visitor::OpsVisitor; -use strata_primitives::buf::Buf32; -use strata_state::tx::ProtocolOperation; +use strata_state::{ + batch::SignedBatchCheckpoint, + tx::{DepositInfo, ProtocolOperation}, +}; /// Ops visitor for rollup client. -// TODO: add db manager -pub struct ClientOpsVisitor; +#[derive(Clone, Debug)] +pub struct ClientOpsVisitor { + ops: Vec, + // TODO: Add l1 manager to store da to db +} + +impl ClientOpsVisitor { + pub fn new() -> Self { + Self { ops: Vec::new() } + } +} impl OpsVisitor for ClientOpsVisitor { - fn visit_da(&self, _d: &[u8]) -> ProtocolOperation { - // TODO: insert in db, when we have DA - ProtocolOperation::DaCommitment(Buf32::zero()) + fn collect(self) -> Vec { + self.ops + } + + fn visit_da<'a>(&mut self, data: impl Iterator) { + let mut hasher = Sha256::new(); + for d in data { + hasher.update(d); + } + let hash: [u8; 32] = hasher.finalize().into(); + self.ops.push(ProtocolOperation::DaCommitment(hash.into())); + } + + fn visit_deposit(&mut self, d: DepositInfo) { + self.ops.push(ProtocolOperation::Deposit(d)); + } + + fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { + self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } } + +// #[cfg(test)] +// mod test { +// //Helper function to create a test block with given transactions +// fn create_test_block(transactions: Vec) -> Block { +// let bhash = BlockHash::from_byte_array([0; 32]); +// Block { +// header: Header { +// version: BVersion::ONE, +// prev_blockhash: bhash, +// merkle_root: TxMerkleNode::from_byte_array(*bhash.as_byte_array()), +// time: 100, +// bits: CompactTarget::from_consensus(1), +// nonce: 1, +// }, +// txdata: transactions, +// } +// } +// #[test] +// fn test_filter_relevant_txs_deposit() { +// let params = gen_params(); +// let filter_config = create_tx_filter_config(¶ms); +// let deposit_config = filter_config.deposit_config.clone(); +// let ee_addr = vec![1u8; 20]; // Example EVM address +// let params = gen_params(); +// let deposit_script = +// build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); +// +// let tx = create_test_deposit_tx( +// Amount::from_sat(deposit_config.deposit_amount), +// &deposit_config.address.address().script_pubkey(), +// &deposit_script, +// ); +// +// let block = create_test_block(vec![tx]); +// +// let result = +// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); +// +// assert_eq!(result.len(), 1, "Should find one relevant transaction"); +// assert_eq!( +// result[0].index(), +// 0, +// "The relevant transaction should be the first one" +// ); +// +// for op in result[0].proto_ops() { +// if let ProtocolOperation::Deposit(deposit_info) = op { +// assert_eq!(deposit_info.address, ee_addr, "EE address should match"); +// assert_eq!( +// deposit_info.amt, +// BitcoinAmount::from_sat(deposit_config.deposit_amount), +// "Deposit amount should match" +// ); +// } else { +// panic!("Expected Deposit info"); +// } +// } +// } +// +// #[test] +// fn test_filter_multiple_ops_in_single_tx() { +// let params = gen_params(); +// let filter_config = create_tx_filter_config(¶ms); +// let deposit_config = filter_config.deposit_config.clone(); +// let ee_addr = vec![1u8; 20]; // Example EVM address +// +// // Create deposit utxo +// let deposit_script = +// build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); +// +// let mut tx = create_test_deposit_tx( +// Amount::from_sat(deposit_config.deposit_amount), +// &deposit_config.address.address().script_pubkey(), +// &deposit_script, +// ); +// +// // Create envelope tx and copy its input to the above deposit tx +// let num_envelopes = 1; +// let envtx = create_checkpoint_envelope_tx(¶ms, num_envelopes); +// tx.input.push(envtx.input[0].clone()); +// +// // Create a block with single tx that has multiple ops +// let block = create_test_block(vec![tx]); +// +// let result = +// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); +// +// assert_eq!(result.len(), 1, "Should find one relevant transaction"); +// assert_eq!( +// result[0].proto_ops().len(), +// 2, +// "Should find two protocol ops" +// ); +// +// let mut dep_count = 0; +// let mut ckpt_count = 0; +// for op in result[0].proto_ops() { +// match op { +// ProtocolOperation::Deposit(_) => dep_count += 1, +// ProtocolOperation::Checkpoint(_) => ckpt_count += 1, +// _ => {} +// } +// } +// assert_eq!(dep_count, 1, "should have one deposit"); +// assert_eq!(ckpt_count, 1, "should have one checkpoint"); +// } +// +// #[test] +// fn test_filter_relevant_txs_deposit_request() { +// let params = gen_params(); +// let filter_config = create_tx_filter_config(¶ms); +// let mut deposit_config = filter_config.deposit_config.clone(); +// let params = gen_params(); +// let extra_amt = 10000; +// deposit_config.deposit_amount += extra_amt; +// let dest_addr = vec![2u8; 20]; // Example EVM address +// let dummy_block = [0u8; 32]; // Example dummy block +// let deposit_request_script = build_test_deposit_request_script( +// deposit_config.magic_bytes.clone(), +// dummy_block.to_vec(), +// dest_addr.clone(), +// ); +// +// let tx = create_test_deposit_tx( +// Amount::from_sat(deposit_config.deposit_amount), // Any amount +// &deposit_config.address.address().script_pubkey(), +// &deposit_request_script, +// ); +// +// let block = create_test_block(vec![tx]); +// +// let result = +// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); +// +// assert_eq!(result.len(), 1, "Should find one relevant transaction"); +// assert_eq!( +// result[0].index(), +// 0, +// "The relevant transaction should be the first one" +// ); +// +// for op in result[0].proto_ops() { +// if let ProtocolOperation::DepositRequest(deposit_req_info) = op { +// assert_eq!( +// deposit_req_info.address, dest_addr, +// "EE address should match" +// ); +// assert_eq!( +// deposit_req_info.take_back_leaf_hash, dummy_block, +// "Control block should match" +// ); +// } else { +// panic!("Expected DepositRequest info"); +// } +// } +// } +// +// #[test] +// fn test_filter_relevant_txs_no_deposit() { +// let params = gen_params(); +// let filter_config = create_tx_filter_config(¶ms); +// let deposit_config = filter_config.deposit_config.clone(); +// let params = gen_params(); +// let irrelevant_tx = create_test_deposit_tx( +// Amount::from_sat(deposit_config.deposit_amount), +// &test_taproot_addr().address().script_pubkey(), +// &ScriptBuf::new(), +// ); +// +// let block = create_test_block(vec![irrelevant_tx]); +// +// let result = +// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); +// +// assert!( +// result.is_empty(), +// "Should not find any relevant transactions" +// ); +// } +// +// #[test] +// fn test_filter_relevant_txs_multiple_deposits() { +// let params = gen_params(); +// let filter_config = create_tx_filter_config(¶ms); +// let deposit_config = filter_config.deposit_config.clone(); +// let params = gen_params(); +// let dest_addr1 = vec![3u8; 20]; +// let dest_addr2 = vec![4u8; 20]; +// +// let deposit_script1 = +// build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr1.clone()); +// let deposit_script2 = +// build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr2.clone()); +// +// let tx1 = create_test_deposit_tx( +// Amount::from_sat(deposit_config.deposit_amount), +// &deposit_config.address.address().script_pubkey(), +// &deposit_script1, +// ); +// let tx2 = create_test_deposit_tx( +// Amount::from_sat(deposit_config.deposit_amount), +// &deposit_config.address.address().script_pubkey(), +// &deposit_script2, +// ); +// +// let block = create_test_block(vec![tx1, tx2]); +// +// let result = +// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); +// +// assert_eq!(result.len(), 2, "Should find two relevant transactions"); +// assert_eq!( +// result[0].index(), +// 0, +// "First relevant transaction should be at index 0" +// ); +// assert_eq!( +// result[1].index(), +// 1, +// "Second relevant transaction should be at index 1" +// ); +// +// for (i, info) in result +// .iter() +// .flat_map(|op_txs| op_txs.proto_ops()) +// .enumerate() +// { +// if let ProtocolOperation::Deposit(deposit_info) = info { +// assert_eq!( +// deposit_info.address, +// if i == 0 { +// dest_addr1.clone() +// } else { +// dest_addr2.clone() +// }, +// "EVM address should match for transaction {}", +// i +// ); +// assert_eq!( +// deposit_info.amt, +// BitcoinAmount::from_sat(deposit_config.deposit_amount), +// "Deposit amount should match for transaction {}", +// i +// ); +// } else { +// panic!("Expected Deposit info for transaction {}", i); +// } +// } +// } +// } diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index 4343a1646..7b73cbcd2 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -8,7 +8,10 @@ use anyhow::bail; use bitcoin::{Block, BlockHash}; use strata_config::btcio::ReaderConfig; use strata_l1tx::{ - filter::{filter_protocol_op_tx_refs, TxFilterConfig}, + filter::{ + visitor::{BlockIndexer, DepositRequestIndexer, OpIndexer}, + TxFilterConfig, + }, messages::{BlockData, L1Event}, }; use strata_primitives::params::Params; @@ -321,12 +324,21 @@ async fn process_block( block: Block, ) -> anyhow::Result<(L1Event, BlockHash)> { let txs = block.txdata.len(); - let params = ctx.params.clone(); - let visitor = ClientOpsVisitor; - let filtered_tx_refs = - filter_protocol_op_tx_refs(&block, ctx.params.rollup(), state.filter_config(), &visitor); - let block_data = BlockData::new(height, block, filtered_tx_refs); + + // Index ops + let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); + let tx_refs = ops_indexer + .index_block(&block, state.filter_config()) + .collect(); + + // Index deposit requests + let _dep_reqs = DepositRequestIndexer::new() + .index_block(&block, state.filter_config()) + .collect(); + + let block_data = BlockData::new(height, block, tx_refs); + let l1blkid = block_data.block().block_hash(); trace!(%height, %l1blkid, %txs, "fetched block from client"); @@ -413,6 +425,7 @@ pub async fn get_verification_state( #[cfg(test)] mod test { use bitcoin::{hashes::Hash, Network}; + use strata_l1tx::filter::types::EnvelopeTags; use strata_primitives::{ buf::Buf32, l1::{BitcoinAddress, L1Status}, @@ -453,9 +466,12 @@ mod test { } } - fn get_filter_config(name: &str) -> TxFilterConfig { + fn get_filter_config() -> TxFilterConfig { TxFilterConfig { - rollup_name: name.to_string(), + envelope_tags: EnvelopeTags { + checkpoint_tag: "test-checkpt".to_string(), + da_tag: "test-da".to_string(), + }, expected_addrs: SortedVec::new(), expected_blobs: SortedVec::new(), expected_outpoints: SortedVec::new(), @@ -474,7 +490,7 @@ mod test { // Get reader state with 10 recent blocks fn get_reader_state(ctx: &ReaderContext) -> ReaderState { - let filter_config = get_filter_config("zkzkzk"); + let filter_config = get_filter_config(); let recent_blocks: [Buf32; N_RECENT_BLOCKS] = ArbitraryGenerator::new().generate(); let recent_blocks: VecDeque = recent_blocks .into_iter() diff --git a/crates/l1tx/src/envelope/parser.rs b/crates/l1tx/src/envelope/parser.rs index 42d0c21a4..f54c54db6 100644 --- a/crates/l1tx/src/envelope/parser.rs +++ b/crates/l1tx/src/envelope/parser.rs @@ -3,14 +3,14 @@ use bitcoin::{ script::{Instruction, Instructions}, ScriptBuf, }; -use strata_primitives::{ - l1::payload::{L1Payload, L1PayloadType}, - params::RollupParams, -}; +use strata_primitives::l1::payload::{L1Payload, L1PayloadType}; use thiserror::Error; use tracing::*; -use crate::utils::{next_bytes, next_op}; +use crate::{ + filter::TxFilterConfig, + utils::{next_bytes, next_op}, +}; /// Errors that can be generated while parsing envelopes. #[derive(Debug, Error)] @@ -36,14 +36,14 @@ pub enum EnvelopeParseError { /// This function errors if it cannot parse the [`L1Payload`] pub fn parse_envelope_payloads( script: &ScriptBuf, - params: &RollupParams, + filter_conf: &TxFilterConfig, ) -> Result, EnvelopeParseError> { let mut instructions = script.instructions(); let mut payloads = Vec::new(); // TODO: make this sophisticated, i.e. even if one payload parsing fails, continue finding other // envelopes and extracting payloads. Or is that really necessary? - while let Ok(payload) = parse_l1_payload(&mut instructions, params) { + while let Ok(payload) = parse_l1_payload(&mut instructions, filter_conf) { payloads.push(payload); } Ok(payloads) @@ -51,13 +51,13 @@ pub fn parse_envelope_payloads( fn parse_l1_payload( instructions: &mut Instructions, - params: &RollupParams, + filter_conf: &TxFilterConfig, ) -> Result { enter_envelope(instructions)?; // Parse type let ptype = next_bytes(instructions) - .and_then(|bytes| parse_payload_type(bytes, params)) + .and_then(|bytes| parse_payload_type(bytes, filter_conf)) .ok_or(EnvelopeParseError::InvalidTypeTag)?; // Parse payload @@ -65,10 +65,10 @@ fn parse_l1_payload( Ok(L1Payload::new(payload, ptype)) } -fn parse_payload_type(tag_bytes: &[u8], params: &RollupParams) -> Option { - if params.checkpoint_tag.as_bytes() == tag_bytes { +fn parse_payload_type(tag_bytes: &[u8], filter_conf: &TxFilterConfig) -> Option { + if filter_conf.envelope_tags.checkpoint_tag.as_bytes() == tag_bytes { Some(L1PayloadType::Checkpoint) - } else if params.da_tag.as_bytes() == tag_bytes { + } else if filter_conf.envelope_tags.da_tag.as_bytes() == tag_bytes { Some(L1PayloadType::Da) } else { None @@ -136,12 +136,13 @@ mod tests { fn test_parse_envelope_data() { let bytes = vec![0, 1, 2, 3]; let params = gen_params(); + let filter_config = TxFilterConfig::derive_from(params.rollup()).unwrap(); let envelope1 = L1Payload::new_checkpoint(bytes.clone()); let envelope2 = L1Payload::new_checkpoint(bytes.clone()); let script = generate_envelope_script_test(&[envelope1.clone(), envelope2.clone()], ¶ms) .unwrap(); - let result = parse_envelope_payloads(&script, params.rollup()).unwrap(); + let result = parse_envelope_payloads(&script, &filter_config).unwrap(); assert_eq!(result, vec![envelope1, envelope2]); @@ -151,7 +152,7 @@ mod tests { let script = generate_envelope_script_test(&[envelope_data.clone()], ¶ms).unwrap(); // Parse the rollup name - let result = parse_envelope_payloads(&script, params.rollup()).unwrap(); + let result = parse_envelope_payloads(&script, &filter_config).unwrap(); // Assert the rollup name was parsed correctly assert_eq!(result, vec![envelope_data]); diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index 4363c3a98..ba4c2d424 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -1,62 +1,21 @@ -use bitcoin::{Block, Transaction}; -use strata_primitives::{l1::payload::L1PayloadType, params::RollupParams}; +use bitcoin::Transaction; +use strata_primitives::l1::payload::L1PayloadType; use strata_state::{ batch::SignedBatchCheckpoint, - tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, + tx::{DepositInfo, DepositRequestInfo}, }; use tracing::warn; -mod types; +pub mod types; pub mod visitor; pub use types::TxFilterConfig; -use visitor::OpsVisitor; -use super::messages::ProtocolTxEntry; use crate::{ deposit::{deposit_request::extract_deposit_request_info, deposit_tx::extract_deposit_info}, envelope::parser::parse_envelope_payloads, }; -/// Filter protocol operations as refs from relevant [`Transaction`]s in a block based on given -/// [`TxFilterConfig`]s -pub fn filter_protocol_op_tx_refs( - block: &Block, - params: &RollupParams, - filter_config: &TxFilterConfig, - ops_visitor: &V, -) -> Vec { - block - .txdata - .iter() - .enumerate() - .map(|(i, tx)| { - let protocol_ops = extract_protocol_ops(tx, params, filter_config, ops_visitor); - ProtocolTxEntry::new(i as u32, protocol_ops) - }) - // Filter out tx refs which do not contain protocol ops - .filter(|txref| !txref.proto_ops().is_empty()) - .collect() -} - -/// If a [`Transaction`] is relevant based on given [`RelevantTxType`]s then we extract relevant -/// info. -fn extract_protocol_ops( - tx: &Transaction, - params: &RollupParams, - filter_conf: &TxFilterConfig, - ops_visitor: &V, -) -> Vec { - // Currently all we have are envelope txs, deposits and deposit requests - parse_envelope_checkpoints(tx, params) - .map(|c| ops_visitor.visit_checkpoint(c)) - .chain(parse_deposits(tx, filter_conf).map(|x| ops_visitor.visit_deposit(x))) - .chain( - parse_deposit_requests(tx, filter_conf).map(|x| ops_visitor.visit_deposit_request(x)), - ) - .collect() -} - fn parse_deposit_requests( tx: &Transaction, filter_conf: &TxFilterConfig, @@ -65,6 +24,7 @@ fn parse_deposit_requests( extract_deposit_request_info(tx, &filter_conf.deposit_config).into_iter() } +/// Parse deposits from [`Transaction`]. fn parse_deposits( tx: &Transaction, filter_conf: &TxFilterConfig, @@ -73,18 +33,27 @@ fn parse_deposits( extract_deposit_info(tx, &filter_conf.deposit_config).into_iter() } +/// Parse da blobs from [`Transaction`]. +fn parse_da<'a>( + _tx: &'a Transaction, + _filter_conf: &TxFilterConfig, +) -> impl Iterator { + // TODO: implement this when we have da + std::iter::empty() +} + /// Parses envelope from the given transaction. Currently, the only envelope recognizable is /// the checkpoint envelope. // TODO: we need to change envelope structure and possibly have envelopes for checkpoints and // DA separately -fn parse_envelope_checkpoints<'a>( +fn parse_envelopes<'a>( tx: &'a Transaction, - params: &'a RollupParams, + filter_conf: &'a TxFilterConfig, ) -> impl Iterator + 'a { tx.input.iter().flat_map(|inp| { inp.witness .tapscript() - .and_then(|scr| parse_envelope_payloads(&scr.into(), params).ok()) + .and_then(|scr| parse_envelope_payloads(&scr.into(), filter_conf).ok()) .map(|items| { items .into_iter() @@ -109,14 +78,11 @@ mod test { use bitcoin::{ absolute::{Height, LockTime}, - block::{Header, Version as BVersion}, - hashes::Hash, key::{Parity, UntweakedKeypair}, secp256k1::{XOnlyPublicKey, SECP256K1}, taproot::{ControlBlock, LeafVersion, TaprootMerkleBranch}, transaction::Version, - Address, Amount, Block, BlockHash, CompactTarget, Network, ScriptBuf, TapNodeHash, - Transaction, TxMerkleNode, TxOut, + Address, Amount, Network, ScriptBuf, TapNodeHash, Transaction, TxOut, }; use rand::{rngs::OsRng, RngCore}; use strata_btcio::test_utils::{build_reveal_transaction_test, generate_envelope_script_test}; @@ -124,24 +90,20 @@ mod test { l1::{payload::L1Payload, BitcoinAmount}, params::Params, }; - use strata_state::{batch::SignedBatchCheckpoint, tx::ProtocolOperation}; + use strata_state::batch::SignedBatchCheckpoint; use strata_test_utils::{l2::gen_params, ArbitraryGenerator}; - use super::{visitor::OpsVisitor, TxFilterConfig}; + use super::TxFilterConfig; use crate::{ deposit::test_utils::{ build_test_deposit_request_script, build_test_deposit_script, create_test_deposit_tx, test_taproot_addr, }, - filter::filter_protocol_op_tx_refs, + filter::{parse_deposit_requests, parse_deposits, parse_envelopes}, }; const OTHER_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; - struct TestVisitor; - - impl OpsVisitor for TestVisitor {} - /// Helper function to create filter config fn create_tx_filter_config(params: &Params) -> TxFilterConfig { TxFilterConfig::derive_from(params.rollup()).expect("can't get filter config") @@ -165,22 +127,6 @@ mod test { } } - /// Helper function to create a test block with given transactions - fn create_test_block(transactions: Vec) -> Block { - let bhash = BlockHash::from_byte_array([0; 32]); - Block { - header: Header { - version: BVersion::ONE, - prev_blockhash: bhash, - merkle_root: TxMerkleNode::from_byte_array(*bhash.as_byte_array()), - time: 100, - bits: CompactTarget::from_consensus(1), - nonce: 1, - }, - txdata: transactions, - } - } - fn parse_addr(addr: &str) -> Address { Address::from_str(addr) .unwrap() @@ -222,79 +168,33 @@ mod test { } #[test] - fn test_filter_relevant_txs_with_rollup_envelope() { + fn test_parse_envelopes() { // Test with valid name - let params: Params = gen_params(); + let mut params: Params = gen_params(); let filter_config = create_tx_filter_config(¶ms); // Testing multiple envelopes are parsed let num_envelopes = 2; let tx = create_checkpoint_envelope_tx(¶ms, num_envelopes); - let block = create_test_block(vec![tx]); - - let tx_refs = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); + let checkpoints: Vec<_> = parse_envelopes(&tx, &filter_config).collect(); - assert_eq!(tx_refs.len(), 1, "There's one tx containing protocol ops"); - assert_eq!( - tx_refs[0].proto_ops().len(), - 2, - "Should filter relevant envelopes" - ); + assert_eq!(checkpoints.len(), 2, "Should filter relevant envelopes"); // Test with invalid checkpoint tag - let mut new_params = params.clone(); - new_params.rollup.checkpoint_tag = "invalid_checkpoint_tag".to_string(); - - let tx = create_checkpoint_envelope_tx(&new_params, 2); - let block = create_test_block(vec![tx]); - let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); - assert!(result.is_empty(), "Should filter out irrelevant txs"); - } - - #[test] - fn test_filter_relevant_txs_no_match() { - let tx1 = create_test_tx(vec![create_test_txout(1000, &parse_addr(OTHER_ADDR))]); - let tx2 = create_test_tx(vec![create_test_txout(10000, &parse_addr(OTHER_ADDR))]); - let params = gen_params(); - let block = create_test_block(vec![tx1, tx2]); - let filter_config = create_tx_filter_config(¶ms); - - let txids: Vec = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor) - .iter() - .map(|op_refs| op_refs.index()) - .collect(); - assert!(txids.is_empty()); // No transactions match - } - - #[test] - fn test_filter_relevant_txs_multiple_matches() { - let params: Params = gen_params(); + params.rollup.checkpoint_tag = "invalid_checkpoint_tag".to_string(); let filter_config = create_tx_filter_config(¶ms); - let tx1 = create_checkpoint_envelope_tx(¶ms, 1); - let tx2 = create_test_tx(vec![create_test_txout(100, &parse_addr(OTHER_ADDR))]); - let tx3 = create_checkpoint_envelope_tx(¶ms, 1); - let block = create_test_block(vec![tx1, tx2, tx3]); - let txids: Vec = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor) - .iter() - .map(|op_refs| op_refs.index()) - .collect(); - // First and third txs match - assert_eq!(txids[0], 0); - assert_eq!(txids[1], 2); + let tx = create_checkpoint_envelope_tx(¶ms, 2); + let checkpoints: Vec<_> = parse_envelopes(&tx, &filter_config).collect(); + assert!(checkpoints.is_empty(), "There should be no envelopes"); } #[test] - fn test_filter_relevant_txs_deposit() { + fn test_parse_deposit_txs() { let params = gen_params(); - let filter_config = create_tx_filter_config(¶ms); - let deposit_config = filter_config.deposit_config.clone(); + let filter_conf = create_tx_filter_config(¶ms); + let deposit_config = filter_conf.deposit_config.clone(); let ee_addr = vec![1u8; 20]; // Example EVM address - let params = gen_params(); let deposit_script = build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); @@ -303,221 +203,65 @@ mod test { &deposit_config.address.address().script_pubkey(), &deposit_script, ); - - let block = create_test_block(vec![tx]); - - let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); - - assert_eq!(result.len(), 1, "Should find one relevant transaction"); + let deposits: Vec<_> = parse_deposits(&tx, &filter_conf).collect(); + assert_eq!(deposits.len(), 1, "Should find one deposit transaction"); + assert_eq!(deposits[0].address, ee_addr, "EE address should match"); assert_eq!( - result[0].index(), - 0, - "The relevant transaction should be the first one" + deposits[0].amt, + BitcoinAmount::from_sat(deposit_config.deposit_amount), + "Deposit amount should match" ); - - for op in result[0].proto_ops() { - if let ProtocolOperation::Deposit(deposit_info) = op { - assert_eq!(deposit_info.address, ee_addr, "EE address should match"); - assert_eq!( - deposit_info.amt, - BitcoinAmount::from_sat(deposit_config.deposit_amount), - "Deposit amount should match" - ); - } else { - panic!("Expected Deposit info"); - } - } } #[test] - fn test_filter_multiple_ops_in_single_tx() { + fn test_parse_deposit_request() { let params = gen_params(); - let filter_config = create_tx_filter_config(¶ms); - let deposit_config = filter_config.deposit_config.clone(); - let ee_addr = vec![1u8; 20]; // Example EVM address - - // Create deposit utxo - let deposit_script = - build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); - - let mut tx = create_test_deposit_tx( - Amount::from_sat(deposit_config.deposit_amount), - &deposit_config.address.address().script_pubkey(), - &deposit_script, - ); - - // Create envelope tx and copy its input to the above deposit tx - let num_envelopes = 1; - let envtx = create_checkpoint_envelope_tx(¶ms, num_envelopes); - tx.input.push(envtx.input[0].clone()); - - // Create a block with single tx that has multiple ops - let block = create_test_block(vec![tx]); - - let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); + let filter_conf = create_tx_filter_config(¶ms); + let mut deposit_conf = filter_conf.deposit_config.clone(); - assert_eq!(result.len(), 1, "Should find one relevant transaction"); - assert_eq!( - result[0].proto_ops().len(), - 2, - "Should find two protocol ops" - ); - - let mut dep_count = 0; - let mut ckpt_count = 0; - for op in result[0].proto_ops() { - match op { - ProtocolOperation::Deposit(_) => dep_count += 1, - ProtocolOperation::Checkpoint(_) => ckpt_count += 1, - _ => {} - } - } - assert_eq!(dep_count, 1, "should have one deposit"); - assert_eq!(ckpt_count, 1, "should have one checkpoint"); - } - - #[test] - fn test_filter_relevant_txs_deposit_request() { - let params = gen_params(); - let filter_config = create_tx_filter_config(¶ms); - let mut deposit_config = filter_config.deposit_config.clone(); - let params = gen_params(); let extra_amt = 10000; - deposit_config.deposit_amount += extra_amt; + deposit_conf.deposit_amount += extra_amt; let dest_addr = vec![2u8; 20]; // Example EVM address let dummy_block = [0u8; 32]; // Example dummy block let deposit_request_script = build_test_deposit_request_script( - deposit_config.magic_bytes.clone(), + deposit_conf.magic_bytes.clone(), dummy_block.to_vec(), dest_addr.clone(), ); let tx = create_test_deposit_tx( - Amount::from_sat(deposit_config.deposit_amount), // Any amount - &deposit_config.address.address().script_pubkey(), + Amount::from_sat(deposit_conf.deposit_amount), // Any amount + &deposit_conf.address.address().script_pubkey(), &deposit_request_script, ); - let block = create_test_block(vec![tx]); - - let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); + let deposit_reqs: Vec<_> = parse_deposit_requests(&tx, &filter_conf).collect(); + assert_eq!(deposit_reqs.len(), 1, "Should find one deposit request"); - assert_eq!(result.len(), 1, "Should find one relevant transaction"); assert_eq!( - result[0].index(), - 0, - "The relevant transaction should be the first one" + deposit_reqs[0].address, dest_addr, + "EE address should match" + ); + assert_eq!( + deposit_reqs[0].take_back_leaf_hash, dummy_block, + "Control block should match" ); - - for op in result[0].proto_ops() { - if let ProtocolOperation::DepositRequest(deposit_req_info) = op { - assert_eq!( - deposit_req_info.address, dest_addr, - "EE address should match" - ); - assert_eq!( - deposit_req_info.take_back_leaf_hash, dummy_block, - "Control block should match" - ); - } else { - panic!("Expected DepositRequest info"); - } - } } + /// Tests parsing deposits which are invalid, i.e won't parse. #[test] - fn test_filter_relevant_txs_no_deposit() { - let params = gen_params(); - let filter_config = create_tx_filter_config(¶ms); - let deposit_config = filter_config.deposit_config.clone(); + fn test_parse_invalid_deposit() { let params = gen_params(); - let irrelevant_tx = create_test_deposit_tx( - Amount::from_sat(deposit_config.deposit_amount), + let filter_conf = create_tx_filter_config(¶ms); + let deposit_conf = filter_conf.deposit_config.clone(); + // This won't have magic bytes in script so shouldn't get parsed. + let tx = create_test_deposit_tx( + Amount::from_sat(deposit_conf.deposit_amount), &test_taproot_addr().address().script_pubkey(), &ScriptBuf::new(), ); - let block = create_test_block(vec![irrelevant_tx]); - - let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); - - assert!( - result.is_empty(), - "Should not find any relevant transactions" - ); - } - - #[test] - fn test_filter_relevant_txs_multiple_deposits() { - let params = gen_params(); - let filter_config = create_tx_filter_config(¶ms); - let deposit_config = filter_config.deposit_config.clone(); - let params = gen_params(); - let dest_addr1 = vec![3u8; 20]; - let dest_addr2 = vec![4u8; 20]; - - let deposit_script1 = - build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr1.clone()); - let deposit_script2 = - build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr2.clone()); - - let tx1 = create_test_deposit_tx( - Amount::from_sat(deposit_config.deposit_amount), - &deposit_config.address.address().script_pubkey(), - &deposit_script1, - ); - let tx2 = create_test_deposit_tx( - Amount::from_sat(deposit_config.deposit_amount), - &deposit_config.address.address().script_pubkey(), - &deposit_script2, - ); - - let block = create_test_block(vec![tx1, tx2]); - - let result = - filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); - - assert_eq!(result.len(), 2, "Should find two relevant transactions"); - assert_eq!( - result[0].index(), - 0, - "First relevant transaction should be at index 0" - ); - assert_eq!( - result[1].index(), - 1, - "Second relevant transaction should be at index 1" - ); - - for (i, info) in result - .iter() - .flat_map(|op_txs| op_txs.proto_ops()) - .enumerate() - { - if let ProtocolOperation::Deposit(deposit_info) = info { - assert_eq!( - deposit_info.address, - if i == 0 { - dest_addr1.clone() - } else { - dest_addr2.clone() - }, - "EVM address should match for transaction {}", - i - ); - assert_eq!( - deposit_info.amt, - BitcoinAmount::from_sat(deposit_config.deposit_amount), - "Deposit amount should match for transaction {}", - i - ); - } else { - panic!("Expected Deposit info for transaction {}", i); - } - } + let deposits: Vec<_> = parse_deposits(&tx, &filter_conf).collect(); + assert!(deposits.is_empty(), "Should find no deposit request"); } } diff --git a/crates/l1tx/src/filter/types.rs b/crates/l1tx/src/filter/types.rs index 0c82f47c1..a711ca342 100644 --- a/crates/l1tx/src/filter/types.rs +++ b/crates/l1tx/src/filter/types.rs @@ -8,11 +8,17 @@ use strata_primitives::{ use crate::utils::{generate_taproot_address, get_operator_wallet_pks}; +#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct EnvelopeTags { + pub checkpoint_tag: String, + pub da_tag: String, +} + /// A configuration that determines how relevant transactions in a bitcoin block are filtered. #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct TxFilterConfig { - /// For checkpoint update envelopes. - pub rollup_name: String, + /// Envelope tag names + pub envelope_tags: EnvelopeTags, /// For addresses that are expected to be spent to. pub expected_addrs: SortedVec, @@ -39,6 +45,11 @@ impl TxFilterConfig { let expected_addrs = SortedVec::from(vec![address.clone()]); let expected_outpoints = SortedVec::new(); + let envelope_tags = EnvelopeTags { + checkpoint_tag: rollup_params.checkpoint_tag.clone(), + da_tag: rollup_params.da_tag.clone(), + }; + let deposit_config = DepositTxParams { magic_bytes: rollup_name.clone().into_bytes(), address_length: rollup_params.address_length, @@ -46,7 +57,7 @@ impl TxFilterConfig { address, }; Ok(Self { - rollup_name, + envelope_tags, expected_blobs, expected_addrs, expected_outpoints, diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/visitor.rs index d50ec7488..3fba0535e 100644 --- a/crates/l1tx/src/filter/visitor.rs +++ b/crates/l1tx/src/filter/visitor.rs @@ -1,29 +1,105 @@ -use strata_primitives::hash; +use bitcoin::{Block, Transaction}; use strata_state::{ batch::SignedBatchCheckpoint, tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, }; +use super::{parse_da, parse_deposit_requests, parse_deposits, parse_envelopes, TxFilterConfig}; +use crate::messages::ProtocolTxEntry; + pub trait OpsVisitor { // Do stuffs with `SignedBatchCheckpoint`. - fn visit_checkpoint(&self, chkpt: SignedBatchCheckpoint) -> ProtocolOperation { - ProtocolOperation::Checkpoint(chkpt) - } + fn visit_checkpoint(&mut self, _chkpt: SignedBatchCheckpoint) {} // Do stuffs with `DepositInfo`. - fn visit_deposit(&self, d: DepositInfo) -> ProtocolOperation { - ProtocolOperation::Deposit(d) - } + fn visit_deposit(&mut self, _d: DepositInfo) {} // Do stuffs with `DepositRequest`. - fn visit_deposit_request(&self, d: DepositRequestInfo) -> ProtocolOperation { - ProtocolOperation::DepositRequest(d) - } + fn visit_deposit_request(&mut self, _d: DepositRequestInfo) {} // Do stuffs with DA. - fn visit_da(&self, d: &[u8]) -> ProtocolOperation { - // TODO: this default implementation might change when we actually have DA - let commitment = hash::raw(d); - ProtocolOperation::DaCommitment(commitment) + fn visit_da<'a>(&mut self, _d: impl Iterator) {} + + fn collect(self) -> Vec; +} + +pub trait BlockIndexer { + type Output; + + fn collect(self) -> Self::Output; + + fn index_tx(&mut self, tx: &Transaction, config: &TxFilterConfig); + + fn index_block(mut self, block: &Block, config: &TxFilterConfig) -> Self + where + Self: Sized, + { + for (_i, tx) in block.txdata.iter().enumerate() { + self.index_tx(tx, config); + } + self + } +} + +#[derive(Clone, Debug)] +pub struct OpIndexer { + visitor: V, + tx_entries: Vec, +} + +impl OpIndexer { + pub fn new(visitor: V) -> Self { + Self { + visitor, + tx_entries: Vec::new(), + } + } +} + +#[derive(Clone, Default)] +pub struct DepositRequestIndexer { + requests: Vec, +} + +impl DepositRequestIndexer { + pub fn new() -> Self { + Self { + ..Default::default() + } + } +} + +impl BlockIndexer for OpIndexer { + type Output = Vec; + + fn index_tx(&mut self, tx: &Transaction, config: &TxFilterConfig) { + let mut visitor = self.visitor.clone(); + for chp in parse_envelopes(tx, config) { + visitor.visit_checkpoint(chp); + } + for dp in parse_deposits(tx, config) { + visitor.visit_deposit(dp); + } + let da = parse_da(tx, config); + visitor.visit_da(da); + + let entry = ProtocolTxEntry::new(1, visitor.collect()); + self.tx_entries.push(entry); + } + + fn collect(self) -> Self::Output { + self.tx_entries + } +} + +impl BlockIndexer for DepositRequestIndexer { + type Output = Vec; + + fn collect(self) -> Self::Output { + self.requests + } + + fn index_tx(&mut self, tx: &Transaction, config: &TxFilterConfig) { + self.requests = parse_deposit_requests(tx, config).collect(); } } diff --git a/crates/proof-impl/btc-blockspace/Cargo.toml b/crates/proof-impl/btc-blockspace/Cargo.toml index d4b095e78..b68699075 100644 --- a/crates/proof-impl/btc-blockspace/Cargo.toml +++ b/crates/proof-impl/btc-blockspace/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" [dependencies] bitcoin = { workspace = true, features = ["serde"] } borsh.workspace = true +digest.workspace = true serde = { workspace = true, features = ["derive"] } sha2.workspace = true strata-bridge-tx-builder.workspace = true diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index 7a2c85f26..883057ab5 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -2,17 +2,52 @@ //! deposits, forced inclusion transactions as well as state updates use bitcoin::Block; -use strata_l1tx::filter::{filter_protocol_op_tx_refs, visitor::OpsVisitor, TxFilterConfig}; +use digest::Digest; +use sha2::Sha256; +use strata_l1tx::filter::{ + visitor::{BlockIndexer, OpIndexer, OpsVisitor}, + TxFilterConfig, +}; use strata_primitives::{block_credential::CredRule, params::RollupParams}; use strata_state::{ - batch::BatchCheckpoint, + batch::{BatchCheckpoint, SignedBatchCheckpoint}, tx::{DepositInfo, ProtocolOperation}, }; /// Ops visitor for Prover. -pub struct ProverOpsVisitor; +#[derive(Debug, Clone)] +struct ProverOpsVisitor { + ops: Vec, +} + +impl ProverOpsVisitor { + pub fn new() -> Self { + Self { ops: Vec::new() } + } +} + +impl OpsVisitor for ProverOpsVisitor { + fn collect(self) -> Vec { + self.ops + } -impl OpsVisitor for ProverOpsVisitor {} + fn visit_da<'a>(&mut self, data: impl Iterator) { + let mut hasher = Sha256::new(); + for d in data { + hasher.update(d); + } + let hash: [u8; 32] = hasher.finalize().into(); + self.ops.push(ProtocolOperation::DaCommitment(hash.into())); + } + + fn visit_deposit(&mut self, d: DepositInfo) { + self.ops.push(ProtocolOperation::Deposit(d)); + } + + fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { + self.ops.push(ProtocolOperation::Checkpoint(chkpt)); + } +} pub fn extract_relevant_info( block: &Block, @@ -24,9 +59,10 @@ pub fn extract_relevant_info( // Just pass a no-op to the filter function as prover does not have to do anything with the raw // data like storing in db. - let txrefs = filter_protocol_op_tx_refs(block, rollup_params, filter_config, &ProverOpsVisitor); + let indexer = OpIndexer::new(ProverOpsVisitor::new()); + let tx_refs = indexer.index_block(block, filter_config).collect(); - for op in txrefs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { + for op in tx_refs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { match op { ProtocolOperation::Deposit(deposit_info) => { deposits.push(deposit_info.clone()); From b97c24221bed281e008b064493e598bdb1a7189a Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Mon, 27 Jan 2025 12:46:10 +0545 Subject: [PATCH 11/22] Move around bitcoin test fns --- crates/btcio/src/reader/ops_visitor.rs | 33 ++++---- crates/l1tx/src/deposit/deposit_request.rs | 9 ++- crates/l1tx/src/deposit/deposit_tx.rs | 6 +- crates/l1tx/src/deposit/test_utils.rs | 87 +--------------------- crates/l1tx/src/filter/mod.rs | 21 +++--- crates/l1tx/src/filter/visitor.rs | 6 +- crates/test-utils/src/bitcoin.rs | 84 ++++++++++++++++++++- 7 files changed, 125 insertions(+), 121 deletions(-) diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/ops_visitor.rs index 0af971d79..39e2cccd7 100644 --- a/crates/btcio/src/reader/ops_visitor.rs +++ b/crates/btcio/src/reader/ops_visitor.rs @@ -44,21 +44,24 @@ impl OpsVisitor for ClientOpsVisitor { // #[cfg(test)] // mod test { -// //Helper function to create a test block with given transactions -// fn create_test_block(transactions: Vec) -> Block { -// let bhash = BlockHash::from_byte_array([0; 32]); -// Block { -// header: Header { -// version: BVersion::ONE, -// prev_blockhash: bhash, -// merkle_root: TxMerkleNode::from_byte_array(*bhash.as_byte_array()), -// time: 100, -// bits: CompactTarget::from_consensus(1), -// nonce: 1, -// }, -// txdata: transactions, -// } -// } +// use bitcoin::{block::Header, BlockHash, CompactTarget, TxMerkleNode}; +// use strata_test_utils::{l2::gen_params, ArbitraryGenerator}; +// +// //Helper function to create a test block with given transactions +// fn create_test_block(transactions: Vec) -> Block { +// let bhash = BlockHash::from_byte_array([0; 32]); +// Block { +// header: Header { +// version: BVersion::ONE, +// prev_blockhash: bhash, +// merkle_root: TxMerkleNode::from_byte_array(*bhash.as_byte_array()), +// time: 100, +// bits: CompactTarget::from_consensus(1), +// nonce: 1, +// }, +// txdata: transactions, +// } +// } // #[test] // fn test_filter_relevant_txs_deposit() { // let params = gen_params(); diff --git a/crates/l1tx/src/deposit/deposit_request.rs b/crates/l1tx/src/deposit/deposit_request.rs index f6474d5e8..6d9fe5a1d 100644 --- a/crates/l1tx/src/deposit/deposit_request.rs +++ b/crates/l1tx/src/deposit/deposit_request.rs @@ -105,15 +105,16 @@ pub fn parse_deposit_request_script( #[cfg(test)] mod tests { use bitcoin::{absolute::LockTime, Amount, Transaction}; + use strata_test_utils::bitcoin::{ + build_no_op_deposit_request_script, build_test_deposit_request_script, + create_test_deposit_tx, + }; use super::extract_deposit_request_info; use crate::deposit::{ deposit_request::parse_deposit_request_script, error::DepositParseError, - test_utils::{ - build_no_op_deposit_request_script, build_test_deposit_request_script, - create_test_deposit_tx, get_deposit_tx_config, test_taproot_addr, - }, + test_utils::{get_deposit_tx_config, test_taproot_addr}, }; #[test] diff --git a/crates/l1tx/src/deposit/deposit_tx.rs b/crates/l1tx/src/deposit/deposit_tx.rs index 010792a28..9c0e38405 100644 --- a/crates/l1tx/src/deposit/deposit_tx.rs +++ b/crates/l1tx/src/deposit/deposit_tx.rs @@ -85,13 +85,11 @@ fn parse_deposit_script<'a>( mod tests { use bitcoin::Amount; + use strata_test_utils::bitcoin::{build_test_deposit_script, create_test_deposit_tx}; use crate::deposit::{ deposit_tx::extract_deposit_info, - test_utils::{ - build_test_deposit_script, create_test_deposit_tx, get_deposit_tx_config, - test_taproot_addr, - }, + test_utils::{get_deposit_tx_config, test_taproot_addr}, }; #[test] diff --git a/crates/l1tx/src/deposit/test_utils.rs b/crates/l1tx/src/deposit/test_utils.rs index 6f6e49e9d..3798d73ba 100644 --- a/crates/l1tx/src/deposit/test_utils.rs +++ b/crates/l1tx/src/deposit/test_utils.rs @@ -1,16 +1,7 @@ use std::str::FromStr; -use bitcoin::{ - absolute::LockTime, - opcodes::all::OP_RETURN, - script::{self, PushBytesBuf}, - Address, Amount, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, -}; -use strata_primitives::{ - l1::{BitcoinAddress, OutputRef}, - params::DepositTxParams, -}; -use strata_test_utils::ArbitraryGenerator; +use bitcoin::Address; +use strata_primitives::{l1::BitcoinAddress, params::DepositTxParams}; pub fn test_taproot_addr() -> BitcoinAddress { let addr = @@ -30,77 +21,3 @@ pub fn get_deposit_tx_config() -> DepositTxParams { address: test_taproot_addr(), } } - -pub fn create_test_deposit_tx( - amt: Amount, - addr_script: &ScriptBuf, - opreturn_script: &ScriptBuf, -) -> Transaction { - let previous_output: OutputRef = ArbitraryGenerator::new().generate(); - - let inputs = vec![TxIn { - previous_output: *previous_output.outpoint(), - script_sig: Default::default(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - witness: Witness::new(), - }]; - - // Construct the outputs - let outputs = vec![ - TxOut { - value: amt, // 10 BTC in satoshis - script_pubkey: addr_script.clone(), - }, - TxOut { - value: Amount::ZERO, // Amount is zero for OP_RETURN - script_pubkey: opreturn_script.clone(), - }, - ]; - - // Create the transaction - Transaction { - version: bitcoin::transaction::Version(2), - lock_time: LockTime::ZERO, - input: inputs, - output: outputs, - } -} - -pub fn build_no_op_deposit_request_script( - magic: Vec, - dummy_block: Vec, - dest_addr: Vec, -) -> ScriptBuf { - let builder = script::Builder::new() - .push_slice(PushBytesBuf::try_from(magic).unwrap()) - .push_slice(PushBytesBuf::try_from(dummy_block).unwrap()) - .push_slice(PushBytesBuf::try_from(dest_addr).unwrap()); - - builder.into_script() -} - -pub fn build_test_deposit_request_script( - magic: Vec, - dummy_block: Vec, - dest_addr: Vec, -) -> ScriptBuf { - let mut data = magic; - data.extend(dummy_block); - data.extend(dest_addr); - let builder = script::Builder::new() - .push_opcode(OP_RETURN) - .push_slice(PushBytesBuf::try_from(data).unwrap()); - - builder.into_script() -} - -pub fn build_test_deposit_script(magic: Vec, dest_addr: Vec) -> ScriptBuf { - let mut data = magic; - data.extend(dest_addr); - - let builder = script::Builder::new() - .push_opcode(OP_RETURN) - .push_slice(PushBytesBuf::try_from(data).unwrap()); - - builder.into_script() -} diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index ba4c2d424..7ed5e3cdb 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -46,7 +46,7 @@ fn parse_da<'a>( /// the checkpoint envelope. // TODO: we need to change envelope structure and possibly have envelopes for checkpoints and // DA separately -fn parse_envelopes<'a>( +fn parse_checkpoint_envelopes<'a>( tx: &'a Transaction, filter_conf: &'a TxFilterConfig, ) -> impl Iterator + 'a { @@ -91,15 +91,18 @@ mod test { params::Params, }; use strata_state::batch::SignedBatchCheckpoint; - use strata_test_utils::{l2::gen_params, ArbitraryGenerator}; + use strata_test_utils::{ + bitcoin::{ + build_test_deposit_request_script, build_test_deposit_script, create_test_deposit_tx, + }, + l2::gen_params, + ArbitraryGenerator, + }; use super::TxFilterConfig; use crate::{ - deposit::test_utils::{ - build_test_deposit_request_script, build_test_deposit_script, create_test_deposit_tx, - test_taproot_addr, - }, - filter::{parse_deposit_requests, parse_deposits, parse_envelopes}, + deposit::test_utils::test_taproot_addr, + filter::{parse_checkpoint_envelopes, parse_deposit_requests, parse_deposits}, }; const OTHER_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; @@ -176,7 +179,7 @@ mod test { // Testing multiple envelopes are parsed let num_envelopes = 2; let tx = create_checkpoint_envelope_tx(¶ms, num_envelopes); - let checkpoints: Vec<_> = parse_envelopes(&tx, &filter_config).collect(); + let checkpoints: Vec<_> = parse_checkpoint_envelopes(&tx, &filter_config).collect(); assert_eq!(checkpoints.len(), 2, "Should filter relevant envelopes"); @@ -185,7 +188,7 @@ mod test { let filter_config = create_tx_filter_config(¶ms); let tx = create_checkpoint_envelope_tx(¶ms, 2); - let checkpoints: Vec<_> = parse_envelopes(&tx, &filter_config).collect(); + let checkpoints: Vec<_> = parse_checkpoint_envelopes(&tx, &filter_config).collect(); assert!(checkpoints.is_empty(), "There should be no envelopes"); } diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/visitor.rs index 3fba0535e..0cf3315d8 100644 --- a/crates/l1tx/src/filter/visitor.rs +++ b/crates/l1tx/src/filter/visitor.rs @@ -4,7 +4,9 @@ use strata_state::{ tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, }; -use super::{parse_da, parse_deposit_requests, parse_deposits, parse_envelopes, TxFilterConfig}; +use super::{ + parse_checkpoint_envelopes, parse_da, parse_deposit_requests, parse_deposits, TxFilterConfig, +}; use crate::messages::ProtocolTxEntry; pub trait OpsVisitor { @@ -74,7 +76,7 @@ impl BlockIndexer for OpIndexer { fn index_tx(&mut self, tx: &Transaction, config: &TxFilterConfig) { let mut visitor = self.visitor.clone(); - for chp in parse_envelopes(tx, config) { + for chp in parse_checkpoint_envelopes(tx, config) { visitor.visit_checkpoint(chp); } for dp in parse_deposits(tx, config) { diff --git a/crates/test-utils/src/bitcoin.rs b/crates/test-utils/src/bitcoin.rs index c5dd40fcd..eaa61eb3f 100644 --- a/crates/test-utils/src/bitcoin.rs +++ b/crates/test-utils/src/bitcoin.rs @@ -1,13 +1,19 @@ use std::collections::HashMap; use bitcoin::{ + absolute::LockTime, block::Header, consensus::{deserialize, serialize}, hashes::Hash, - Block, Transaction, + opcodes::all::OP_RETURN, + script::{self, PushBytesBuf}, + Amount, Block, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, }; use strata_l1tx::filter::TxFilterConfig; -use strata_primitives::{buf::Buf32, l1::L1BlockRecord}; +use strata_primitives::{ + buf::Buf32, + l1::{L1BlockRecord, OutputRef}, +}; use strata_state::l1::{ get_difficulty_adjustment_height, BtcParams, HeaderVerificationState, L1BlockId, TimestampStore, }; @@ -176,3 +182,77 @@ pub fn get_test_tx_filter_config() -> TxFilterConfig { let config = gen_params(); TxFilterConfig::derive_from(config.rollup()).expect("can't derive filter config") } + +pub fn create_test_deposit_tx( + amt: Amount, + addr_script: &ScriptBuf, + opreturn_script: &ScriptBuf, +) -> Transaction { + let previous_output: OutputRef = ArbitraryGenerator::new().generate(); + + let inputs = vec![TxIn { + previous_output: *previous_output.outpoint(), + script_sig: Default::default(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::new(), + }]; + + // Construct the outputs + let outputs = vec![ + TxOut { + value: amt, // 10 BTC in satoshis + script_pubkey: addr_script.clone(), + }, + TxOut { + value: Amount::ZERO, // Amount is zero for OP_RETURN + script_pubkey: opreturn_script.clone(), + }, + ]; + + // Create the transaction + Transaction { + version: bitcoin::transaction::Version(2), + lock_time: LockTime::ZERO, + input: inputs, + output: outputs, + } +} + +pub fn build_no_op_deposit_request_script( + magic: Vec, + dummy_block: Vec, + dest_addr: Vec, +) -> ScriptBuf { + let builder = script::Builder::new() + .push_slice(PushBytesBuf::try_from(magic).unwrap()) + .push_slice(PushBytesBuf::try_from(dummy_block).unwrap()) + .push_slice(PushBytesBuf::try_from(dest_addr).unwrap()); + + builder.into_script() +} + +pub fn build_test_deposit_request_script( + magic: Vec, + dummy_block: Vec, + dest_addr: Vec, +) -> ScriptBuf { + let mut data = magic; + data.extend(dummy_block); + data.extend(dest_addr); + let builder = script::Builder::new() + .push_opcode(OP_RETURN) + .push_slice(PushBytesBuf::try_from(data).unwrap()); + + builder.into_script() +} + +pub fn build_test_deposit_script(magic: Vec, dest_addr: Vec) -> ScriptBuf { + let mut data = magic; + data.extend(dest_addr); + + let builder = script::Builder::new() + .push_opcode(OP_RETURN) + .push_slice(PushBytesBuf::try_from(data).unwrap()); + + builder.into_script() +} From f0e73c850424fcf910ea88d4f492f37265362693 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Mon, 27 Jan 2025 16:50:11 +0545 Subject: [PATCH 12/22] Rearrange test utils --- Cargo.lock | 1 + crates/btcio/Cargo.toml | 1 + crates/btcio/src/reader/ops_visitor.rs | 526 +++++++++--------- crates/btcio/src/reader/query.rs | 4 +- crates/btcio/src/test_utils.rs | 54 +- crates/l1tx/src/deposit/deposit_request.rs | 7 +- crates/l1tx/src/deposit/deposit_tx.rs | 9 +- crates/l1tx/src/deposit/test_utils.rs | 16 +- crates/l1tx/src/filter/mod.rs | 96 +--- crates/l1tx/src/filter/visitor.rs | 22 +- .../proof-impl/btc-blockspace/src/filter.rs | 2 +- crates/test-utils/src/bitcoin.rs | 16 +- 12 files changed, 379 insertions(+), 375 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92c4ee4d6..b32b830fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12877,6 +12877,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bitcoin", + "borsh", "bytes", "corepc-node", "digest 0.10.7", diff --git a/crates/btcio/Cargo.toml b/crates/btcio/Cargo.toml index 809cfbbf7..2b475ed91 100644 --- a/crates/btcio/Cargo.toml +++ b/crates/btcio/Cargo.toml @@ -34,6 +34,7 @@ tokio.workspace = true tracing.workspace = true [dev-dependencies] +borsh.workspace = true strata-common.workspace = true strata-rocksdb = { workspace = true, features = ["test_utils"] } strata-state = { workspace = true, features = ["test_utils"] } diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/ops_visitor.rs index 39e2cccd7..69f12fc5b 100644 --- a/crates/btcio/src/reader/ops_visitor.rs +++ b/crates/btcio/src/reader/ops_visitor.rs @@ -24,7 +24,7 @@ impl OpsVisitor for ClientOpsVisitor { self.ops } - fn visit_da<'a>(&mut self, data: impl Iterator) { + fn visit_da<'a>(&mut self, data: &'a [&'a [u8]]) { let mut hasher = Sha256::new(); for d in data { hasher.update(d); @@ -42,256 +42,274 @@ impl OpsVisitor for ClientOpsVisitor { } } -// #[cfg(test)] -// mod test { -// use bitcoin::{block::Header, BlockHash, CompactTarget, TxMerkleNode}; -// use strata_test_utils::{l2::gen_params, ArbitraryGenerator}; -// -// //Helper function to create a test block with given transactions -// fn create_test_block(transactions: Vec) -> Block { -// let bhash = BlockHash::from_byte_array([0; 32]); -// Block { -// header: Header { -// version: BVersion::ONE, -// prev_blockhash: bhash, -// merkle_root: TxMerkleNode::from_byte_array(*bhash.as_byte_array()), -// time: 100, -// bits: CompactTarget::from_consensus(1), -// nonce: 1, -// }, -// txdata: transactions, -// } -// } -// #[test] -// fn test_filter_relevant_txs_deposit() { -// let params = gen_params(); -// let filter_config = create_tx_filter_config(¶ms); -// let deposit_config = filter_config.deposit_config.clone(); -// let ee_addr = vec![1u8; 20]; // Example EVM address -// let params = gen_params(); -// let deposit_script = -// build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); -// -// let tx = create_test_deposit_tx( -// Amount::from_sat(deposit_config.deposit_amount), -// &deposit_config.address.address().script_pubkey(), -// &deposit_script, -// ); -// -// let block = create_test_block(vec![tx]); -// -// let result = -// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); -// -// assert_eq!(result.len(), 1, "Should find one relevant transaction"); -// assert_eq!( -// result[0].index(), -// 0, -// "The relevant transaction should be the first one" -// ); -// -// for op in result[0].proto_ops() { -// if let ProtocolOperation::Deposit(deposit_info) = op { -// assert_eq!(deposit_info.address, ee_addr, "EE address should match"); -// assert_eq!( -// deposit_info.amt, -// BitcoinAmount::from_sat(deposit_config.deposit_amount), -// "Deposit amount should match" -// ); -// } else { -// panic!("Expected Deposit info"); -// } -// } -// } -// -// #[test] -// fn test_filter_multiple_ops_in_single_tx() { -// let params = gen_params(); -// let filter_config = create_tx_filter_config(¶ms); -// let deposit_config = filter_config.deposit_config.clone(); -// let ee_addr = vec![1u8; 20]; // Example EVM address -// -// // Create deposit utxo -// let deposit_script = -// build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); -// -// let mut tx = create_test_deposit_tx( -// Amount::from_sat(deposit_config.deposit_amount), -// &deposit_config.address.address().script_pubkey(), -// &deposit_script, -// ); -// -// // Create envelope tx and copy its input to the above deposit tx -// let num_envelopes = 1; -// let envtx = create_checkpoint_envelope_tx(¶ms, num_envelopes); -// tx.input.push(envtx.input[0].clone()); -// -// // Create a block with single tx that has multiple ops -// let block = create_test_block(vec![tx]); -// -// let result = -// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); -// -// assert_eq!(result.len(), 1, "Should find one relevant transaction"); -// assert_eq!( -// result[0].proto_ops().len(), -// 2, -// "Should find two protocol ops" -// ); -// -// let mut dep_count = 0; -// let mut ckpt_count = 0; -// for op in result[0].proto_ops() { -// match op { -// ProtocolOperation::Deposit(_) => dep_count += 1, -// ProtocolOperation::Checkpoint(_) => ckpt_count += 1, -// _ => {} -// } -// } -// assert_eq!(dep_count, 1, "should have one deposit"); -// assert_eq!(ckpt_count, 1, "should have one checkpoint"); -// } -// -// #[test] -// fn test_filter_relevant_txs_deposit_request() { -// let params = gen_params(); -// let filter_config = create_tx_filter_config(¶ms); -// let mut deposit_config = filter_config.deposit_config.clone(); -// let params = gen_params(); -// let extra_amt = 10000; -// deposit_config.deposit_amount += extra_amt; -// let dest_addr = vec![2u8; 20]; // Example EVM address -// let dummy_block = [0u8; 32]; // Example dummy block -// let deposit_request_script = build_test_deposit_request_script( -// deposit_config.magic_bytes.clone(), -// dummy_block.to_vec(), -// dest_addr.clone(), -// ); -// -// let tx = create_test_deposit_tx( -// Amount::from_sat(deposit_config.deposit_amount), // Any amount -// &deposit_config.address.address().script_pubkey(), -// &deposit_request_script, -// ); -// -// let block = create_test_block(vec![tx]); -// -// let result = -// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); -// -// assert_eq!(result.len(), 1, "Should find one relevant transaction"); -// assert_eq!( -// result[0].index(), -// 0, -// "The relevant transaction should be the first one" -// ); -// -// for op in result[0].proto_ops() { -// if let ProtocolOperation::DepositRequest(deposit_req_info) = op { -// assert_eq!( -// deposit_req_info.address, dest_addr, -// "EE address should match" -// ); -// assert_eq!( -// deposit_req_info.take_back_leaf_hash, dummy_block, -// "Control block should match" -// ); -// } else { -// panic!("Expected DepositRequest info"); -// } -// } -// } -// -// #[test] -// fn test_filter_relevant_txs_no_deposit() { -// let params = gen_params(); -// let filter_config = create_tx_filter_config(¶ms); -// let deposit_config = filter_config.deposit_config.clone(); -// let params = gen_params(); -// let irrelevant_tx = create_test_deposit_tx( -// Amount::from_sat(deposit_config.deposit_amount), -// &test_taproot_addr().address().script_pubkey(), -// &ScriptBuf::new(), -// ); -// -// let block = create_test_block(vec![irrelevant_tx]); -// -// let result = -// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); -// -// assert!( -// result.is_empty(), -// "Should not find any relevant transactions" -// ); -// } -// -// #[test] -// fn test_filter_relevant_txs_multiple_deposits() { -// let params = gen_params(); -// let filter_config = create_tx_filter_config(¶ms); -// let deposit_config = filter_config.deposit_config.clone(); -// let params = gen_params(); -// let dest_addr1 = vec![3u8; 20]; -// let dest_addr2 = vec![4u8; 20]; -// -// let deposit_script1 = -// build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr1.clone()); -// let deposit_script2 = -// build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr2.clone()); -// -// let tx1 = create_test_deposit_tx( -// Amount::from_sat(deposit_config.deposit_amount), -// &deposit_config.address.address().script_pubkey(), -// &deposit_script1, -// ); -// let tx2 = create_test_deposit_tx( -// Amount::from_sat(deposit_config.deposit_amount), -// &deposit_config.address.address().script_pubkey(), -// &deposit_script2, -// ); -// -// let block = create_test_block(vec![tx1, tx2]); -// -// let result = -// filter_protocol_op_tx_refs(&block, params.rollup(), &filter_config, &TestVisitor); -// -// assert_eq!(result.len(), 2, "Should find two relevant transactions"); -// assert_eq!( -// result[0].index(), -// 0, -// "First relevant transaction should be at index 0" -// ); -// assert_eq!( -// result[1].index(), -// 1, -// "Second relevant transaction should be at index 1" -// ); -// -// for (i, info) in result -// .iter() -// .flat_map(|op_txs| op_txs.proto_ops()) -// .enumerate() -// { -// if let ProtocolOperation::Deposit(deposit_info) = info { -// assert_eq!( -// deposit_info.address, -// if i == 0 { -// dest_addr1.clone() -// } else { -// dest_addr2.clone() -// }, -// "EVM address should match for transaction {}", -// i -// ); -// assert_eq!( -// deposit_info.amt, -// BitcoinAmount::from_sat(deposit_config.deposit_amount), -// "Deposit amount should match for transaction {}", -// i -// ); -// } else { -// panic!("Expected Deposit info for transaction {}", i); -// } -// } -// } -// } +#[cfg(test)] +mod test { + use bitcoin::{ + block::{Header, Version}, + hashes::Hash, + Amount, Block, BlockHash, CompactTarget, ScriptBuf, Transaction, TxMerkleNode, + }; + use strata_l1tx::filter::{ + visitor::{BlockIndexer, OpIndexer}, + TxFilterConfig, + }; + use strata_primitives::{ + l1::{payload::L1Payload, BitcoinAmount}, + params::Params, + }; + use strata_state::{batch::SignedBatchCheckpoint, tx::ProtocolOperation}; + use strata_test_utils::{ + bitcoin::{ + build_test_deposit_request_script, build_test_deposit_script, create_test_deposit_tx, + test_taproot_addr, + }, + l2::gen_params, + ArbitraryGenerator, + }; + + use crate::{reader::ops_visitor::ClientOpsVisitor, test_utils::create_checkpoint_envelope_tx}; + + const TEST_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; + + //Helper function to create a test block with given transactions + fn create_test_block(transactions: Vec) -> Block { + let bhash = BlockHash::from_byte_array([0; 32]); + Block { + header: Header { + version: Version::ONE, + prev_blockhash: bhash, + merkle_root: TxMerkleNode::from_byte_array(*bhash.as_byte_array()), + time: 100, + bits: CompactTarget::from_consensus(1), + nonce: 1, + }, + txdata: transactions, + } + } + + fn create_tx_filter_config(params: &Params) -> TxFilterConfig { + TxFilterConfig::derive_from(params.rollup()).expect("can't get filter config") + } + + #[test] + fn test_index_deposits() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let ee_addr = vec![1u8; 20]; // Example EVM address + let deposit_script = + build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); + + let tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script, + ); + + let block = create_test_block(vec![tx]); + + let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + + assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); + + for op in tx_entries[0].proto_ops() { + if let ProtocolOperation::Deposit(deposit_info) = op { + assert_eq!(deposit_info.address, ee_addr, "EE address should match"); + assert_eq!( + deposit_info.amt, + BitcoinAmount::from_sat(deposit_config.deposit_amount), + "Deposit amount should match" + ); + } else { + panic!("Expected Deposit info"); + } + } + } + + #[test] + fn test_index_txs_deposit_request() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let mut deposit_config = filter_config.deposit_config.clone(); + + let extra_amt = 10000; + deposit_config.deposit_amount += extra_amt; + let dest_addr = vec![2u8; 20]; // Example EVM address + let dummy_block = [0u8; 32]; // Example dummy block + + let deposit_request_script = build_test_deposit_request_script( + deposit_config.magic_bytes.clone(), + dummy_block.to_vec(), + dest_addr.clone(), + ); + + let tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), // Any amount + &deposit_config.address.address().script_pubkey(), + &deposit_request_script, + ); + + let block = create_test_block(vec![tx]); + + let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + + assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); + + for op in tx_entries[0].proto_ops() { + if let ProtocolOperation::DepositRequest(deposit_req_info) = op { + assert_eq!( + deposit_req_info.address, dest_addr, + "EE address should match" + ); + assert_eq!( + deposit_req_info.take_back_leaf_hash, dummy_block, + "Control block should match" + ); + } else { + panic!("Expected DepositRequest info"); + } + } + } + + #[test] + fn test_index_no_deposit() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let irrelevant_tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &test_taproot_addr().address().script_pubkey(), + &ScriptBuf::new(), + ); + + let block = create_test_block(vec![irrelevant_tx]); + + let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + + assert!( + tx_entries.is_empty(), + "Should not find any relevant transactions" + ); + } + + #[test] + fn test_index_multiple_deposits() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let dest_addr1 = vec![3u8; 20]; + let dest_addr2 = vec![4u8; 20]; + + let deposit_script1 = + build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr1.clone()); + let deposit_script2 = + build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr2.clone()); + + let tx1 = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script1, + ); + let tx2 = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script2, + ); + + let block = create_test_block(vec![tx1, tx2]); + + let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + + assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); + + for (i, info) in tx_entries + .iter() + .flat_map(|op_txs| op_txs.proto_ops()) + .enumerate() + { + if let ProtocolOperation::Deposit(deposit_info) = info { + assert_eq!( + deposit_info.address, + if i == 0 { + dest_addr1.clone() + } else { + dest_addr2.clone() + }, + "EVM address should match for transaction {}", + i + ); + assert_eq!( + deposit_info.amt, + BitcoinAmount::from_sat(deposit_config.deposit_amount), + "Deposit amount should match for transaction {}", + i + ); + } else { + panic!("Expected Deposit info for transaction {}", i); + } + } + } + + #[test] + fn test_index_tx_with_multiple_ops() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let ee_addr = vec![1u8; 20]; // Example EVM address + + // Create deposit utxo + let deposit_script = + build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); + + let mut tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script, + ); + + // Create envelope tx and copy its input to the above deposit tx + let num_envelopes = 1; + let l1_payloads: Vec<_> = (0..num_envelopes) + .map(|_| { + let signed_checkpoint: SignedBatchCheckpoint = ArbitraryGenerator::new().generate(); + L1Payload::new_checkpoint(borsh::to_vec(&signed_checkpoint).unwrap()) + }) + .collect(); + let envtx = create_checkpoint_envelope_tx(¶ms, TEST_ADDR, l1_payloads); + tx.input.push(envtx.input[0].clone()); + + // Create a block with single tx that has multiple ops + let block = create_test_block(vec![tx]); + + let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + println!("TX ENTRIES: {:?}", tx_entries); + + assert_eq!( + tx_entries.len(), + 1, + "Should find one matching transaction entry" + ); + assert_eq!( + tx_entries[0].proto_ops().len(), + 2, + "Should find two protocol ops" + ); + + let mut dep_count = 0; + let mut ckpt_count = 0; + for op in tx_entries[0].proto_ops() { + match op { + ProtocolOperation::Deposit(_) => dep_count += 1, + ProtocolOperation::Checkpoint(_) => ckpt_count += 1, + _ => {} + } + } + assert_eq!(dep_count, 1, "should have one deposit"); + assert_eq!(ckpt_count, 1, "should have one checkpoint"); + } +} diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index 7b73cbcd2..d4be7d4f0 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -328,7 +328,7 @@ async fn process_block( // Index ops let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); - let tx_refs = ops_indexer + let tx_entries = ops_indexer .index_block(&block, state.filter_config()) .collect(); @@ -337,7 +337,7 @@ async fn process_block( .index_block(&block, state.filter_config()) .collect(); - let block_data = BlockData::new(height, block, tx_refs); + let block_data = BlockData::new(height, block, tx_entries); let l1blkid = block_data.block().block_hash(); trace!(%height, %l1blkid, %txs, "fetched block from client"); diff --git a/crates/btcio/src/test_utils.rs b/crates/btcio/src/test_utils.rs index 78d4dff3d..2b534bbf0 100644 --- a/crates/btcio/src/test_utils.rs +++ b/crates/btcio/src/test_utils.rs @@ -1,13 +1,19 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, str::FromStr}; use async_trait::async_trait; use bitcoin::{ + absolute::{Height, LockTime}, bip32::Xpriv, consensus::{self, deserialize}, hashes::Hash, - taproot::ControlBlock, - Address, Amount, Block, BlockHash, Network, ScriptBuf, SignedAmount, Transaction, Txid, Work, + key::{Parity, UntweakedKeypair}, + taproot::{ControlBlock, LeafVersion, TaprootMerkleBranch}, + transaction::Version, + Address, Amount, Block, BlockHash, Network, ScriptBuf, SignedAmount, TapNodeHash, Transaction, + TxOut, Txid, Work, XOnlyPublicKey, }; +use musig2::secp256k1::SECP256K1; +use rand::{rngs::OsRng, RngCore}; use strata_l1tx::envelope::builder::build_envelope_script; use strata_primitives::{l1::payload::L1Payload, params::Params}; @@ -353,6 +359,48 @@ pub mod corepc_node_helpers { } } +// Create an envelope transaction. The focus here is to create a tapscript, rather than a +// completely valid control block. Includes `n_envelopes` envelopes in the tapscript. +pub fn create_checkpoint_envelope_tx( + params: &Params, + address: &str, + l1_payloads: Vec, +) -> Transaction { + let address = Address::from_str(address) + .unwrap() + .require_network(Network::Regtest) + .unwrap(); + let inp_tx = Transaction { + version: Version(1), + lock_time: LockTime::Blocks(Height::from_consensus(1).unwrap()), + input: vec![], + output: vec![TxOut { + value: Amount::from_sat(100000000), + script_pubkey: address.script_pubkey(), + }], + }; + let script = generate_envelope_script_test(&l1_payloads, params).unwrap(); + // Create controlblock + let mut rand_bytes = [0; 32]; + OsRng.fill_bytes(&mut rand_bytes); + let key_pair = UntweakedKeypair::from_seckey_slice(SECP256K1, &rand_bytes).unwrap(); + let public_key = XOnlyPublicKey::from_keypair(&key_pair).0; + let nodehash: [TapNodeHash; 0] = []; + let cb = ControlBlock { + leaf_version: LeafVersion::TapScript, + output_key_parity: Parity::Even, + internal_key: public_key, + merkle_branch: TaprootMerkleBranch::from(nodehash), + }; + + // Create transaction using control block + let mut tx = build_reveal_transaction_test(inp_tx, address, 100, 10, &script, &cb).unwrap(); + tx.input[0].witness.push([1; 3]); + tx.input[0].witness.push(script); + tx.input[0].witness.push(cb.serialize()); + tx +} + #[cfg(test)] pub(crate) mod test_context { use std::sync::Arc; diff --git a/crates/l1tx/src/deposit/deposit_request.rs b/crates/l1tx/src/deposit/deposit_request.rs index 6d9fe5a1d..e624be9f9 100644 --- a/crates/l1tx/src/deposit/deposit_request.rs +++ b/crates/l1tx/src/deposit/deposit_request.rs @@ -107,14 +107,13 @@ mod tests { use bitcoin::{absolute::LockTime, Amount, Transaction}; use strata_test_utils::bitcoin::{ build_no_op_deposit_request_script, build_test_deposit_request_script, - create_test_deposit_tx, + create_test_deposit_tx, test_taproot_addr, }; use super::extract_deposit_request_info; use crate::deposit::{ - deposit_request::parse_deposit_request_script, - error::DepositParseError, - test_utils::{get_deposit_tx_config, test_taproot_addr}, + deposit_request::parse_deposit_request_script, error::DepositParseError, + test_utils::get_deposit_tx_config, }; #[test] diff --git a/crates/l1tx/src/deposit/deposit_tx.rs b/crates/l1tx/src/deposit/deposit_tx.rs index 9c0e38405..7adfc2a27 100644 --- a/crates/l1tx/src/deposit/deposit_tx.rs +++ b/crates/l1tx/src/deposit/deposit_tx.rs @@ -85,13 +85,12 @@ fn parse_deposit_script<'a>( mod tests { use bitcoin::Amount; - use strata_test_utils::bitcoin::{build_test_deposit_script, create_test_deposit_tx}; - - use crate::deposit::{ - deposit_tx::extract_deposit_info, - test_utils::{get_deposit_tx_config, test_taproot_addr}, + use strata_test_utils::bitcoin::{ + build_test_deposit_script, create_test_deposit_tx, test_taproot_addr, }; + use crate::deposit::{deposit_tx::extract_deposit_info, test_utils::get_deposit_tx_config}; + #[test] fn check_deposit_parser() { // values for testing diff --git a/crates/l1tx/src/deposit/test_utils.rs b/crates/l1tx/src/deposit/test_utils.rs index 3798d73ba..099175f18 100644 --- a/crates/l1tx/src/deposit/test_utils.rs +++ b/crates/l1tx/src/deposit/test_utils.rs @@ -1,17 +1,5 @@ -use std::str::FromStr; - -use bitcoin::Address; -use strata_primitives::{l1::BitcoinAddress, params::DepositTxParams}; - -pub fn test_taproot_addr() -> BitcoinAddress { - let addr = - Address::from_str("bcrt1pnmrmugapastum8ztvgwcn8hvq2avmcwh2j4ssru7rtyygkpqq98q4wyd6s") - .unwrap() - .require_network(bitcoin::Network::Regtest) - .unwrap(); - - BitcoinAddress::parse(&addr.to_string(), bitcoin::Network::Regtest).unwrap() -} +use strata_primitives::params::DepositTxParams; +use strata_test_utils::bitcoin::test_taproot_addr; pub fn get_deposit_tx_config() -> DepositTxParams { DepositTxParams { diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index 7ed5e3cdb..ece2c3414 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -33,11 +33,13 @@ fn parse_deposits( extract_deposit_info(tx, &filter_conf.deposit_config).into_iter() } +pub type DaBlobRaw<'a> = &'a [&'a [u8]]; + /// Parse da blobs from [`Transaction`]. -fn parse_da<'a>( +fn parse_da_blobs<'a>( _tx: &'a Transaction, _filter_conf: &TxFilterConfig, -) -> impl Iterator { +) -> impl Iterator> { // TODO: implement this when we have da std::iter::empty() } @@ -74,18 +76,8 @@ fn parse_checkpoint_envelopes<'a>( #[cfg(test)] mod test { - use std::str::FromStr; - - use bitcoin::{ - absolute::{Height, LockTime}, - key::{Parity, UntweakedKeypair}, - secp256k1::{XOnlyPublicKey, SECP256K1}, - taproot::{ControlBlock, LeafVersion, TaprootMerkleBranch}, - transaction::Version, - Address, Amount, Network, ScriptBuf, TapNodeHash, Transaction, TxOut, - }; - use rand::{rngs::OsRng, RngCore}; - use strata_btcio::test_utils::{build_reveal_transaction_test, generate_envelope_script_test}; + use bitcoin::{Amount, ScriptBuf}; + use strata_btcio::test_utils::create_checkpoint_envelope_tx; use strata_primitives::{ l1::{payload::L1Payload, BitcoinAmount}, params::Params, @@ -94,82 +86,22 @@ mod test { use strata_test_utils::{ bitcoin::{ build_test_deposit_request_script, build_test_deposit_script, create_test_deposit_tx, + test_taproot_addr, }, l2::gen_params, ArbitraryGenerator, }; use super::TxFilterConfig; - use crate::{ - deposit::test_utils::test_taproot_addr, - filter::{parse_checkpoint_envelopes, parse_deposit_requests, parse_deposits}, - }; + use crate::filter::{parse_checkpoint_envelopes, parse_deposit_requests, parse_deposits}; - const OTHER_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; + const TEST_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; /// Helper function to create filter config fn create_tx_filter_config(params: &Params) -> TxFilterConfig { TxFilterConfig::derive_from(params.rollup()).expect("can't get filter config") } - /// Helper function to create a test transaction with given txid and outputs - fn create_test_tx(outputs: Vec) -> Transaction { - Transaction { - version: Version(1), - lock_time: LockTime::Blocks(Height::from_consensus(1).unwrap()), - input: vec![], - output: outputs, - } - } - - /// Helper function to create a TxOut with a given address and value - fn create_test_txout(value: u64, address: &Address) -> TxOut { - TxOut { - value: Amount::from_sat(value), - script_pubkey: address.script_pubkey(), - } - } - - fn parse_addr(addr: &str) -> Address { - Address::from_str(addr) - .unwrap() - .require_network(Network::Regtest) - .unwrap() - } - - // Create an envelope transaction. The focus here is to create a tapscript, rather than a - // completely valid control block. Includes `n_envelopes` envelopes in the tapscript. - fn create_checkpoint_envelope_tx(params: &Params, n_envelopes: u32) -> Transaction { - let address = parse_addr(OTHER_ADDR); - let inp_tx = create_test_tx(vec![create_test_txout(100000000, &address)]); - let payloads: Vec<_> = (0..n_envelopes) - .map(|_| { - let signed_checkpoint: SignedBatchCheckpoint = ArbitraryGenerator::new().generate(); - L1Payload::new_checkpoint(borsh::to_vec(&signed_checkpoint).unwrap()) - }) - .collect(); - let script = generate_envelope_script_test(&payloads, params).unwrap(); - // Create controlblock - let mut rand_bytes = [0; 32]; - OsRng.fill_bytes(&mut rand_bytes); - let key_pair = UntweakedKeypair::from_seckey_slice(SECP256K1, &rand_bytes).unwrap(); - let public_key = XOnlyPublicKey::from_keypair(&key_pair).0; - let nodehash: [TapNodeHash; 0] = []; - let cb = ControlBlock { - leaf_version: LeafVersion::TapScript, - output_key_parity: Parity::Even, - internal_key: public_key, - merkle_branch: TaprootMerkleBranch::from(nodehash), - }; - - // Create transaction using control block - let mut tx = build_reveal_transaction_test(inp_tx, address, 100, 10, &script, &cb).unwrap(); - tx.input[0].witness.push([1; 3]); - tx.input[0].witness.push(script); - tx.input[0].witness.push(cb.serialize()); - tx - } - #[test] fn test_parse_envelopes() { // Test with valid name @@ -178,7 +110,13 @@ mod test { // Testing multiple envelopes are parsed let num_envelopes = 2; - let tx = create_checkpoint_envelope_tx(¶ms, num_envelopes); + let l1_payloads: Vec<_> = (0..num_envelopes) + .map(|_| { + let signed_checkpoint: SignedBatchCheckpoint = ArbitraryGenerator::new().generate(); + L1Payload::new_checkpoint(borsh::to_vec(&signed_checkpoint).unwrap()) + }) + .collect(); + let tx = create_checkpoint_envelope_tx(¶ms, TEST_ADDR, l1_payloads.clone()); let checkpoints: Vec<_> = parse_checkpoint_envelopes(&tx, &filter_config).collect(); assert_eq!(checkpoints.len(), 2, "Should filter relevant envelopes"); @@ -187,7 +125,7 @@ mod test { params.rollup.checkpoint_tag = "invalid_checkpoint_tag".to_string(); let filter_config = create_tx_filter_config(¶ms); - let tx = create_checkpoint_envelope_tx(¶ms, 2); + let tx = create_checkpoint_envelope_tx(¶ms, TEST_ADDR, l1_payloads); let checkpoints: Vec<_> = parse_checkpoint_envelopes(&tx, &filter_config).collect(); assert!(checkpoints.is_empty(), "There should be no envelopes"); } diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/visitor.rs index 0cf3315d8..7ea40b2b5 100644 --- a/crates/l1tx/src/filter/visitor.rs +++ b/crates/l1tx/src/filter/visitor.rs @@ -5,7 +5,8 @@ use strata_state::{ }; use super::{ - parse_checkpoint_envelopes, parse_da, parse_deposit_requests, parse_deposits, TxFilterConfig, + parse_checkpoint_envelopes, parse_da_blobs, parse_deposit_requests, parse_deposits, + TxFilterConfig, }; use crate::messages::ProtocolTxEntry; @@ -20,7 +21,7 @@ pub trait OpsVisitor { fn visit_deposit_request(&mut self, _d: DepositRequestInfo) {} // Do stuffs with DA. - fn visit_da<'a>(&mut self, _d: impl Iterator) {} + fn visit_da<'a>(&mut self, _d: &'a [&'a [u8]]) {} fn collect(self) -> Vec; } @@ -30,14 +31,14 @@ pub trait BlockIndexer { fn collect(self) -> Self::Output; - fn index_tx(&mut self, tx: &Transaction, config: &TxFilterConfig); + fn index_tx(&mut self, txidx: u32, tx: &Transaction, config: &TxFilterConfig); fn index_block(mut self, block: &Block, config: &TxFilterConfig) -> Self where Self: Sized, { - for (_i, tx) in block.txdata.iter().enumerate() { - self.index_tx(tx, config); + for (i, tx) in block.txdata.iter().enumerate() { + self.index_tx(i as u32, tx, config); } self } @@ -74,7 +75,7 @@ impl DepositRequestIndexer { impl BlockIndexer for OpIndexer { type Output = Vec; - fn index_tx(&mut self, tx: &Transaction, config: &TxFilterConfig) { + fn index_tx(&mut self, txidx: u32, tx: &Transaction, config: &TxFilterConfig) { let mut visitor = self.visitor.clone(); for chp in parse_checkpoint_envelopes(tx, config) { visitor.visit_checkpoint(chp); @@ -82,10 +83,11 @@ impl BlockIndexer for OpIndexer { for dp in parse_deposits(tx, config) { visitor.visit_deposit(dp); } - let da = parse_da(tx, config); - visitor.visit_da(da); + for da in parse_da_blobs(tx, config) { + visitor.visit_da(da); + } - let entry = ProtocolTxEntry::new(1, visitor.collect()); + let entry = ProtocolTxEntry::new(txidx, visitor.collect()); self.tx_entries.push(entry); } @@ -101,7 +103,7 @@ impl BlockIndexer for DepositRequestIndexer { self.requests } - fn index_tx(&mut self, tx: &Transaction, config: &TxFilterConfig) { + fn index_tx(&mut self, _txidx: u32, tx: &Transaction, config: &TxFilterConfig) { self.requests = parse_deposit_requests(tx, config).collect(); } } diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index 883057ab5..0c476bd38 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -31,7 +31,7 @@ impl OpsVisitor for ProverOpsVisitor { self.ops } - fn visit_da<'a>(&mut self, data: impl Iterator) { + fn visit_da<'a>(&mut self, data: &'a [&'a [u8]]) { let mut hasher = Sha256::new(); for d in data { hasher.update(d); diff --git a/crates/test-utils/src/bitcoin.rs b/crates/test-utils/src/bitcoin.rs index eaa61eb3f..dd047dbdd 100644 --- a/crates/test-utils/src/bitcoin.rs +++ b/crates/test-utils/src/bitcoin.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; use bitcoin::{ absolute::LockTime, @@ -7,12 +7,12 @@ use bitcoin::{ hashes::Hash, opcodes::all::OP_RETURN, script::{self, PushBytesBuf}, - Amount, Block, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, + Address, Amount, Block, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, }; use strata_l1tx::filter::TxFilterConfig; use strata_primitives::{ buf::Buf32, - l1::{L1BlockRecord, OutputRef}, + l1::{BitcoinAddress, L1BlockRecord, OutputRef}, }; use strata_state::l1::{ get_difficulty_adjustment_height, BtcParams, HeaderVerificationState, L1BlockId, TimestampStore, @@ -256,3 +256,13 @@ pub fn build_test_deposit_script(magic: Vec, dest_addr: Vec) -> ScriptBu builder.into_script() } + +pub fn test_taproot_addr() -> BitcoinAddress { + let addr = + Address::from_str("bcrt1pnmrmugapastum8ztvgwcn8hvq2avmcwh2j4ssru7rtyygkpqq98q4wyd6s") + .unwrap() + .require_network(bitcoin::Network::Regtest) + .unwrap(); + + BitcoinAddress::parse(&addr.to_string(), bitcoin::Network::Regtest).unwrap() +} From 8ff2f0ae5dd1f5f9f6061ea00e26e95ea82b7612 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 28 Jan 2025 11:56:02 +0545 Subject: [PATCH 13/22] Update and fix unit tests --- crates/btcio/src/reader/ops_visitor.rs | 29 +++++++++++--------------- crates/l1tx/src/filter/mod.rs | 1 - crates/l1tx/src/filter/visitor.rs | 9 +++++--- crates/rocksdb-store/src/l2/db.rs | 2 +- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/ops_visitor.rs index 69f12fc5b..c8ec7cfb1 100644 --- a/crates/btcio/src/reader/ops_visitor.rs +++ b/crates/btcio/src/reader/ops_visitor.rs @@ -50,7 +50,7 @@ mod test { Amount, Block, BlockHash, CompactTarget, ScriptBuf, Transaction, TxMerkleNode, }; use strata_l1tx::filter::{ - visitor::{BlockIndexer, OpIndexer}, + visitor::{BlockIndexer, DepositRequestIndexer, OpIndexer}, TxFilterConfig, }; use strata_primitives::{ @@ -152,24 +152,19 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + let deposit_req_indexer = DepositRequestIndexer::new(); + let dep_reqs = deposit_req_indexer + .index_block(&block, &filter_config) + .collect(); - assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); + assert_eq!(dep_reqs.len(), 1, "Should find one deposit request"); - for op in tx_entries[0].proto_ops() { - if let ProtocolOperation::DepositRequest(deposit_req_info) = op { - assert_eq!( - deposit_req_info.address, dest_addr, - "EE address should match" - ); - assert_eq!( - deposit_req_info.take_back_leaf_hash, dummy_block, - "Control block should match" - ); - } else { - panic!("Expected DepositRequest info"); - } + for dep_req_info in dep_reqs { + assert_eq!(dep_req_info.address, dest_addr, "EE address should match"); + assert_eq!( + dep_req_info.take_back_leaf_hash, dummy_block, + "Control block should match" + ); } } diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index ece2c3414..684b97cfa 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -123,7 +123,6 @@ mod test { // Test with invalid checkpoint tag params.rollup.checkpoint_tag = "invalid_checkpoint_tag".to_string(); - let filter_config = create_tx_filter_config(¶ms); let tx = create_checkpoint_envelope_tx(¶ms, TEST_ADDR, l1_payloads); let checkpoints: Vec<_> = parse_checkpoint_envelopes(&tx, &filter_config).collect(); diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/visitor.rs index 7ea40b2b5..1cde47dc8 100644 --- a/crates/l1tx/src/filter/visitor.rs +++ b/crates/l1tx/src/filter/visitor.rs @@ -87,8 +87,11 @@ impl BlockIndexer for OpIndexer { visitor.visit_da(da); } - let entry = ProtocolTxEntry::new(txidx, visitor.collect()); - self.tx_entries.push(entry); + let ops = visitor.collect(); + if !ops.is_empty() { + let entry = ProtocolTxEntry::new(txidx, ops); + self.tx_entries.push(entry); + } } fn collect(self) -> Self::Output { @@ -104,6 +107,6 @@ impl BlockIndexer for DepositRequestIndexer { } fn index_tx(&mut self, _txidx: u32, tx: &Transaction, config: &TxFilterConfig) { - self.requests = parse_deposit_requests(tx, config).collect(); + self.requests.extend(parse_deposit_requests(tx, config)); } } diff --git a/crates/rocksdb-store/src/l2/db.rs b/crates/rocksdb-store/src/l2/db.rs index b69fbc616..9c3d74ef0 100644 --- a/crates/rocksdb-store/src/l2/db.rs +++ b/crates/rocksdb-store/src/l2/db.rs @@ -114,7 +114,7 @@ mod tests { use crate::test_utils::get_rocksdb_tmp_instance; fn get_mock_data() -> L2BlockBundle { - let mut arb = ArbitraryGenerator::new_with_size(1 << 12); + let mut arb = ArbitraryGenerator::new_with_size(1 << 14); let l2_block: L2BlockBundle = arb.generate(); l2_block From ad059fb611e9d6f37704dfa007d9bbbee08a4f07 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 28 Jan 2025 12:07:54 +0545 Subject: [PATCH 14/22] Add unit tests for prover ops visitor --- Cargo.lock | 1 + crates/proof-impl/btc-blockspace/Cargo.toml | 3 +- .../proof-impl/btc-blockspace/src/filter.rs | 41 +-- crates/proof-impl/btc-blockspace/src/lib.rs | 1 + .../btc-blockspace/src/ops_visitor.rs | 268 ++++++++++++++++++ 5 files changed, 275 insertions(+), 39 deletions(-) create mode 100644 crates/proof-impl/btc-blockspace/src/ops_visitor.rs diff --git a/Cargo.lock b/Cargo.lock index b32b830fd..df0b4eb8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13266,6 +13266,7 @@ dependencies = [ "serde", "sha2 0.10.8", "strata-bridge-tx-builder", + "strata-btcio", "strata-l1tx", "strata-primitives", "strata-state", diff --git a/crates/proof-impl/btc-blockspace/Cargo.toml b/crates/proof-impl/btc-blockspace/Cargo.toml index b68699075..dac9290c9 100644 --- a/crates/proof-impl/btc-blockspace/Cargo.toml +++ b/crates/proof-impl/btc-blockspace/Cargo.toml @@ -18,5 +18,6 @@ zkaleido.workspace = true [dev-dependencies] hex.workspace = true rand.workspace = true -zkaleido-native-adapter.workspace = true +strata-btcio.workspace = true strata-test-utils.workspace = true +zkaleido-native-adapter.workspace = true diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index 0c476bd38..da0068730 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -2,52 +2,17 @@ //! deposits, forced inclusion transactions as well as state updates use bitcoin::Block; -use digest::Digest; -use sha2::Sha256; use strata_l1tx::filter::{ - visitor::{BlockIndexer, OpIndexer, OpsVisitor}, + visitor::{BlockIndexer, OpIndexer}, TxFilterConfig, }; use strata_primitives::{block_credential::CredRule, params::RollupParams}; use strata_state::{ - batch::{BatchCheckpoint, SignedBatchCheckpoint}, + batch::BatchCheckpoint, tx::{DepositInfo, ProtocolOperation}, }; -/// Ops visitor for Prover. -#[derive(Debug, Clone)] -struct ProverOpsVisitor { - ops: Vec, -} - -impl ProverOpsVisitor { - pub fn new() -> Self { - Self { ops: Vec::new() } - } -} - -impl OpsVisitor for ProverOpsVisitor { - fn collect(self) -> Vec { - self.ops - } - - fn visit_da<'a>(&mut self, data: &'a [&'a [u8]]) { - let mut hasher = Sha256::new(); - for d in data { - hasher.update(d); - } - let hash: [u8; 32] = hasher.finalize().into(); - self.ops.push(ProtocolOperation::DaCommitment(hash.into())); - } - - fn visit_deposit(&mut self, d: DepositInfo) { - self.ops.push(ProtocolOperation::Deposit(d)); - } - - fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { - self.ops.push(ProtocolOperation::Checkpoint(chkpt)); - } -} +use crate::ops_visitor::ProverOpsVisitor; pub fn extract_relevant_info( block: &Block, diff --git a/crates/proof-impl/btc-blockspace/src/lib.rs b/crates/proof-impl/btc-blockspace/src/lib.rs index ab8859192..aec13ec9c 100644 --- a/crates/proof-impl/btc-blockspace/src/lib.rs +++ b/crates/proof-impl/btc-blockspace/src/lib.rs @@ -4,6 +4,7 @@ pub mod block; pub mod filter; pub mod logic; pub mod merkle; +mod ops_visitor; pub mod prover; pub mod scan; pub mod tx; diff --git a/crates/proof-impl/btc-blockspace/src/ops_visitor.rs b/crates/proof-impl/btc-blockspace/src/ops_visitor.rs new file mode 100644 index 000000000..181f714d5 --- /dev/null +++ b/crates/proof-impl/btc-blockspace/src/ops_visitor.rs @@ -0,0 +1,268 @@ +use digest::Digest; +use sha2::Sha256; +use strata_l1tx::filter::visitor::OpsVisitor; +use strata_state::{ + batch::SignedBatchCheckpoint, + tx::{DepositInfo, ProtocolOperation}, +}; + +/// Ops visitor for Prover. +#[derive(Debug, Clone)] +pub(crate) struct ProverOpsVisitor { + ops: Vec, +} + +impl ProverOpsVisitor { + pub fn new() -> Self { + Self { ops: Vec::new() } + } +} + +impl OpsVisitor for ProverOpsVisitor { + fn collect(self) -> Vec { + self.ops + } + + fn visit_da<'a>(&mut self, data: &'a [&'a [u8]]) { + let mut hasher = Sha256::new(); + for d in data { + hasher.update(d); + } + let hash: [u8; 32] = hasher.finalize().into(); + self.ops.push(ProtocolOperation::DaCommitment(hash.into())); + } + + fn visit_deposit(&mut self, d: DepositInfo) { + self.ops.push(ProtocolOperation::Deposit(d)); + } + + fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { + self.ops.push(ProtocolOperation::Checkpoint(chkpt)); + } +} + +/// These are mostly similar to the ones in `strata_btcio::reader::ops_visitor` except for the +/// visitor `ProverOpsVisitor` and indexing of deposit requests. +#[cfg(test)] +mod test { + use bitcoin::{ + block::{Header, Version}, + hashes::Hash, + Amount, Block, BlockHash, CompactTarget, ScriptBuf, Transaction, TxMerkleNode, + }; + use strata_btcio::test_utils::create_checkpoint_envelope_tx; + use strata_l1tx::filter::{ + visitor::{BlockIndexer, OpIndexer}, + TxFilterConfig, + }; + use strata_primitives::{ + l1::{payload::L1Payload, BitcoinAmount}, + params::Params, + }; + use strata_state::{batch::SignedBatchCheckpoint, tx::ProtocolOperation}; + use strata_test_utils::{ + bitcoin::{build_test_deposit_script, create_test_deposit_tx, test_taproot_addr}, + l2::gen_params, + ArbitraryGenerator, + }; + + use super::ProverOpsVisitor; + + const TEST_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; + + //Helper function to create a test block with given transactions + fn create_test_block(transactions: Vec) -> Block { + let bhash = BlockHash::from_byte_array([0; 32]); + Block { + header: Header { + version: Version::ONE, + prev_blockhash: bhash, + merkle_root: TxMerkleNode::from_byte_array(*bhash.as_byte_array()), + time: 100, + bits: CompactTarget::from_consensus(1), + nonce: 1, + }, + txdata: transactions, + } + } + + fn create_tx_filter_config(params: &Params) -> TxFilterConfig { + TxFilterConfig::derive_from(params.rollup()).expect("can't get filter config") + } + + #[test] + fn test_index_deposits() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let ee_addr = vec![1u8; 20]; // Example EVM address + let deposit_script = + build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); + + let tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script, + ); + + let block = create_test_block(vec![tx]); + + let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + + assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); + + for op in tx_entries[0].proto_ops() { + if let ProtocolOperation::Deposit(deposit_info) = op { + assert_eq!(deposit_info.address, ee_addr, "EE address should match"); + assert_eq!( + deposit_info.amt, + BitcoinAmount::from_sat(deposit_config.deposit_amount), + "Deposit amount should match" + ); + } else { + panic!("Expected Deposit info"); + } + } + } + + #[test] + fn test_index_no_deposit() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let irrelevant_tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &test_taproot_addr().address().script_pubkey(), + &ScriptBuf::new(), + ); + + let block = create_test_block(vec![irrelevant_tx]); + + let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + + assert!( + tx_entries.is_empty(), + "Should not find any relevant transactions" + ); + } + + #[test] + fn test_index_multiple_deposits() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let dest_addr1 = vec![3u8; 20]; + let dest_addr2 = vec![4u8; 20]; + + let deposit_script1 = + build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr1.clone()); + let deposit_script2 = + build_test_deposit_script(deposit_config.magic_bytes.clone(), dest_addr2.clone()); + + let tx1 = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script1, + ); + let tx2 = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script2, + ); + + let block = create_test_block(vec![tx1, tx2]); + + let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + + assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); + + for (i, info) in tx_entries + .iter() + .flat_map(|op_txs| op_txs.proto_ops()) + .enumerate() + { + if let ProtocolOperation::Deposit(deposit_info) = info { + assert_eq!( + deposit_info.address, + if i == 0 { + dest_addr1.clone() + } else { + dest_addr2.clone() + }, + "EVM address should match for transaction {}", + i + ); + assert_eq!( + deposit_info.amt, + BitcoinAmount::from_sat(deposit_config.deposit_amount), + "Deposit amount should match for transaction {}", + i + ); + } else { + panic!("Expected Deposit info for transaction {}", i); + } + } + } + + #[test] + fn test_index_tx_with_multiple_ops() { + let params = gen_params(); + let filter_config = create_tx_filter_config(¶ms); + let deposit_config = filter_config.deposit_config.clone(); + let ee_addr = vec![1u8; 20]; // Example EVM address + + // Create deposit utxo + let deposit_script = + build_test_deposit_script(deposit_config.magic_bytes.clone(), ee_addr.clone()); + + let mut tx = create_test_deposit_tx( + Amount::from_sat(deposit_config.deposit_amount), + &deposit_config.address.address().script_pubkey(), + &deposit_script, + ); + + // Create envelope tx and copy its input to the above deposit tx + let num_envelopes = 1; + let l1_payloads: Vec<_> = (0..num_envelopes) + .map(|_| { + let signed_checkpoint: SignedBatchCheckpoint = ArbitraryGenerator::new().generate(); + L1Payload::new_checkpoint(borsh::to_vec(&signed_checkpoint).unwrap()) + }) + .collect(); + let envtx = create_checkpoint_envelope_tx(¶ms, TEST_ADDR, l1_payloads); + tx.input.push(envtx.input[0].clone()); + + // Create a block with single tx that has multiple ops + let block = create_test_block(vec![tx]); + + let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); + let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + println!("TX ENTRIES: {:?}", tx_entries); + + assert_eq!( + tx_entries.len(), + 1, + "Should find one matching transaction entry" + ); + assert_eq!( + tx_entries[0].proto_ops().len(), + 2, + "Should find two protocol ops" + ); + + let mut dep_count = 0; + let mut ckpt_count = 0; + for op in tx_entries[0].proto_ops() { + match op { + ProtocolOperation::Deposit(_) => dep_count += 1, + ProtocolOperation::Checkpoint(_) => ckpt_count += 1, + _ => {} + } + } + assert_eq!(dep_count, 1, "should have one deposit"); + assert_eq!(ckpt_count, 1, "should have one checkpoint"); + } +} From 6b7ed049edcd20488e86726392d5e9b34dfa2487 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 28 Jan 2025 15:07:52 +0545 Subject: [PATCH 15/22] Fix tests --- crates/btcio/src/reader/ops_visitor.rs | 6 +++++- crates/l1tx/src/filter/visitor.rs | 7 +++++++ functional-tests/tests/bridge/bridge_deposit_reclaim.py | 2 +- functional-tests/tests/sync/sync_genesis.py | 4 ++-- functional-tests/tests/sync/sync_genesis_with_btcblocks.py | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/ops_visitor.rs index c8ec7cfb1..92fded5b0 100644 --- a/crates/btcio/src/reader/ops_visitor.rs +++ b/crates/btcio/src/reader/ops_visitor.rs @@ -3,7 +3,7 @@ use sha2::Sha256; use strata_l1tx::filter::visitor::OpsVisitor; use strata_state::{ batch::SignedBatchCheckpoint, - tx::{DepositInfo, ProtocolOperation}, + tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, }; /// Ops visitor for rollup client. @@ -37,6 +37,10 @@ impl OpsVisitor for ClientOpsVisitor { self.ops.push(ProtocolOperation::Deposit(d)); } + fn visit_deposit_request(&mut self, d: DepositRequestInfo) { + self.ops.push(ProtocolOperation::DepositRequest(d)); + } + fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/visitor.rs index 1cde47dc8..67187ab72 100644 --- a/crates/l1tx/src/filter/visitor.rs +++ b/crates/l1tx/src/filter/visitor.rs @@ -80,9 +80,16 @@ impl BlockIndexer for OpIndexer { for chp in parse_checkpoint_envelopes(tx, config) { visitor.visit_checkpoint(chp); } + for dp in parse_deposits(tx, config) { visitor.visit_deposit(dp); } + + // TODO: remove this later when we do not require deposit request ops + for dp in parse_deposit_requests(tx, config) { + visitor.visit_deposit_request(dp); + } + for da in parse_da_blobs(tx, config) { visitor.visit_da(da); } diff --git a/functional-tests/tests/bridge/bridge_deposit_reclaim.py b/functional-tests/tests/bridge/bridge_deposit_reclaim.py index 8c449779e..222d04b2f 100644 --- a/functional-tests/tests/bridge/bridge_deposit_reclaim.py +++ b/functional-tests/tests/bridge/bridge_deposit_reclaim.py @@ -28,7 +28,7 @@ class BridgeDepositReclaimTest(testenv.StrataTester): """ def __init__(self, ctx: flexitest.InitContext): - ctx.set_env("basic") + ctx.set_env(testenv.BasicEnvConfig(101)) def main(self, ctx: flexitest.RunContext): btc = ctx.get_service("bitcoin") diff --git a/functional-tests/tests/sync/sync_genesis.py b/functional-tests/tests/sync/sync_genesis.py index 77d4c2269..c90446327 100644 --- a/functional-tests/tests/sync/sync_genesis.py +++ b/functional-tests/tests/sync/sync_genesis.py @@ -11,7 +11,7 @@ @flexitest.register class SyncGenesisTest(testenv.StrataTester): def __init__(self, ctx: flexitest.InitContext): - ctx.set_env("basic") + ctx.set_env(testenv.BasicEnvConfig(101)) def main(self, ctx: flexitest.RunContext): seq = ctx.get_service("sequencer") @@ -51,5 +51,5 @@ def main(self, ctx: flexitest.RunContext): tip_blkid = stat["chain_tip"] self.debug(f"cur tip slot {tip_slot} blkid {tip_blkid}") assert tip_slot >= last_slot, "cur slot went backwards" - assert tip_slot > last_slot, "seem to not be making progress" + assert tip_slot > last_slot, "seems not to be making progress" last_slot = tip_slot diff --git a/functional-tests/tests/sync/sync_genesis_with_btcblocks.py b/functional-tests/tests/sync/sync_genesis_with_btcblocks.py index ffded6879..823a308e0 100644 --- a/functional-tests/tests/sync/sync_genesis_with_btcblocks.py +++ b/functional-tests/tests/sync/sync_genesis_with_btcblocks.py @@ -11,7 +11,7 @@ @flexitest.register class SyncGenesisTest(testenv.StrataTester): def __init__(self, ctx: flexitest.InitContext): - ctx.set_env("basic") + ctx.set_env(testenv.BasicEnvConfig(101)) def main(self, ctx: flexitest.RunContext): seq = ctx.get_service("sequencer") From 8201c7ebe5c37d05d4d53fda0c23b64d25e5fd09 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 28 Jan 2025 16:57:39 +0545 Subject: [PATCH 16/22] Update visit_da method and some docs --- crates/btcio/src/reader/ops_visitor.rs | 10 ++++++---- crates/l1tx/src/filter/mod.rs | 8 +++----- crates/l1tx/src/filter/visitor.rs | 2 +- crates/proof-impl/btc-blockspace/src/ops_visitor.rs | 9 +++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/ops_visitor.rs index 92fded5b0..cbce0324e 100644 --- a/crates/btcio/src/reader/ops_visitor.rs +++ b/crates/btcio/src/reader/ops_visitor.rs @@ -6,7 +6,8 @@ use strata_state::{ tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, }; -/// Ops visitor for rollup client. +/// Ops visitor for rollup client. This is intended to get Da commitment as well as process/store da +/// blob. #[derive(Clone, Debug)] pub struct ClientOpsVisitor { ops: Vec, @@ -24,12 +25,13 @@ impl OpsVisitor for ClientOpsVisitor { self.ops } - fn visit_da<'a>(&mut self, data: &'a [&'a [u8]]) { + fn visit_da<'a>(&mut self, chunks: impl Iterator) { let mut hasher = Sha256::new(); - for d in data { - hasher.update(d); + for chunk in chunks { + hasher.update(chunk); } let hash: [u8; 32] = hasher.finalize().into(); + // TODO: store da in db self.ops.push(ProtocolOperation::DaCommitment(hash.into())); } diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index 684b97cfa..123873025 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -33,15 +33,13 @@ fn parse_deposits( extract_deposit_info(tx, &filter_conf.deposit_config).into_iter() } -pub type DaBlobRaw<'a> = &'a [&'a [u8]]; - /// Parse da blobs from [`Transaction`]. fn parse_da_blobs<'a>( _tx: &'a Transaction, _filter_conf: &TxFilterConfig, -) -> impl Iterator> { - // TODO: implement this when we have da - std::iter::empty() +) -> impl Iterator + 'a> { + // TODO: actually implement this when we have da + std::iter::empty::>().map(|inner| inner.copied()) } /// Parses envelope from the given transaction. Currently, the only envelope recognizable is diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/visitor.rs index 67187ab72..94c321c75 100644 --- a/crates/l1tx/src/filter/visitor.rs +++ b/crates/l1tx/src/filter/visitor.rs @@ -21,7 +21,7 @@ pub trait OpsVisitor { fn visit_deposit_request(&mut self, _d: DepositRequestInfo) {} // Do stuffs with DA. - fn visit_da<'a>(&mut self, _d: &'a [&'a [u8]]) {} + fn visit_da<'a>(&mut self, _d: impl Iterator) {} fn collect(self) -> Vec; } diff --git a/crates/proof-impl/btc-blockspace/src/ops_visitor.rs b/crates/proof-impl/btc-blockspace/src/ops_visitor.rs index 181f714d5..73978666e 100644 --- a/crates/proof-impl/btc-blockspace/src/ops_visitor.rs +++ b/crates/proof-impl/btc-blockspace/src/ops_visitor.rs @@ -6,7 +6,8 @@ use strata_state::{ tx::{DepositInfo, ProtocolOperation}, }; -/// Ops visitor for Prover. +/// Ops visitor for Prover. Basically this efficiently gets da commitment from chunks without doing +/// anything else with the chunks. #[derive(Debug, Clone)] pub(crate) struct ProverOpsVisitor { ops: Vec, @@ -23,10 +24,10 @@ impl OpsVisitor for ProverOpsVisitor { self.ops } - fn visit_da<'a>(&mut self, data: &'a [&'a [u8]]) { + fn visit_da<'a>(&mut self, chunks: impl Iterator) { let mut hasher = Sha256::new(); - for d in data { - hasher.update(d); + for chunk in chunks { + hasher.update(chunk); } let hash: [u8; 32] = hasher.finalize().into(); self.ops.push(ProtocolOperation::DaCommitment(hash.into())); From a3d7ef0a37d93f62b414cf6a34a2ac89e2f0c47d Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Thu, 30 Jan 2025 18:11:03 +0545 Subject: [PATCH 17/22] Restructure indexers, collect deposit requests and da entries --- crates/btcio/src/reader/mod.rs | 2 +- crates/btcio/src/reader/query.rs | 13 ++-- .../reader/{ops_visitor.rs => tx_indexer.rs} | 77 ++++++++++++------- .../src/filter/{visitor.rs => indexer.rs} | 61 ++++++--------- crates/l1tx/src/filter/mod.rs | 2 +- crates/l1tx/src/messages.rs | 16 ++++ .../proof-impl/btc-blockspace/src/filter.rs | 8 +- crates/proof-impl/btc-blockspace/src/lib.rs | 2 +- .../src/{ops_visitor.rs => tx_indexer.rs} | 45 ++++++----- .../src/block_template/block_assembly.rs | 2 +- crates/state/src/tx.rs | 2 +- provers/sp1/Cargo.toml | 1 + 12 files changed, 126 insertions(+), 105 deletions(-) rename crates/btcio/src/reader/{ops_visitor.rs => tx_indexer.rs} (82%) rename crates/l1tx/src/filter/{visitor.rs => indexer.rs} (67%) rename crates/proof-impl/btc-blockspace/src/{ops_visitor.rs => tx_indexer.rs} (88%) diff --git a/crates/btcio/src/reader/mod.rs b/crates/btcio/src/reader/mod.rs index 06d97b42e..2b9b9343c 100644 --- a/crates/btcio/src/reader/mod.rs +++ b/crates/btcio/src/reader/mod.rs @@ -1,3 +1,3 @@ -mod ops_visitor; pub mod query; mod state; +mod tx_indexer; diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index d4be7d4f0..66c61db19 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -9,7 +9,7 @@ use bitcoin::{Block, BlockHash}; use strata_config::btcio::ReaderConfig; use strata_l1tx::{ filter::{ - visitor::{BlockIndexer, DepositRequestIndexer, OpIndexer}, + indexer::{BlockIndexer, OpIndexer}, TxFilterConfig, }, messages::{BlockData, L1Event}, @@ -24,7 +24,7 @@ use tokio::sync::mpsc; use tracing::*; use crate::{ - reader::{ops_visitor::ClientOpsVisitor, state::ReaderState}, + reader::{state::ReaderState, tx_indexer::ClientTxIndexer}, rpc::traits::ReaderRpc, status::{apply_status_updates, L1StatusUpdate}, }; @@ -327,15 +327,12 @@ async fn process_block( let params = ctx.params.clone(); // Index ops - let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); - let tx_entries = ops_indexer + let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); + let (tx_entries, _dep_reqs, _da_entries) = ops_indexer .index_block(&block, state.filter_config()) .collect(); - // Index deposit requests - let _dep_reqs = DepositRequestIndexer::new() - .index_block(&block, state.filter_config()) - .collect(); + // TODO: do stuffs with dep_reqs and da_entries let block_data = BlockData::new(height, block, tx_entries); diff --git a/crates/btcio/src/reader/ops_visitor.rs b/crates/btcio/src/reader/tx_indexer.rs similarity index 82% rename from crates/btcio/src/reader/ops_visitor.rs rename to crates/btcio/src/reader/tx_indexer.rs index cbce0324e..955a5830b 100644 --- a/crates/btcio/src/reader/ops_visitor.rs +++ b/crates/btcio/src/reader/tx_indexer.rs @@ -1,38 +1,49 @@ use digest::Digest; use sha2::Sha256; -use strata_l1tx::filter::visitor::OpsVisitor; +use strata_l1tx::{filter::indexer::TxIndexer, messages::DaEntry}; +use strata_primitives::buf::Buf32; use strata_state::{ batch::SignedBatchCheckpoint, tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, }; -/// Ops visitor for rollup client. This is intended to get Da commitment as well as process/store da -/// blob. +/// Ops indexer for rollup client. Collects extra info like da blobs and deposit requests #[derive(Clone, Debug)] -pub struct ClientOpsVisitor { +pub struct ClientTxIndexer { ops: Vec, - // TODO: Add l1 manager to store da to db + deposit_requests: Vec, + da_entries: Vec, } -impl ClientOpsVisitor { +impl ClientTxIndexer { pub fn new() -> Self { - Self { ops: Vec::new() } + Self { + ops: Vec::new(), + deposit_requests: Vec::new(), + da_entries: Vec::new(), + } } -} -impl OpsVisitor for ClientOpsVisitor { - fn collect(self) -> Vec { - self.ops + fn ops(&self) -> &[ProtocolOperation] { + &self.ops } +} +impl TxIndexer for ClientTxIndexer { fn visit_da<'a>(&mut self, chunks: impl Iterator) { let mut hasher = Sha256::new(); + let mut blob = Vec::new(); + for chunk in chunks { hasher.update(chunk); + blob.extend_from_slice(chunk); } let hash: [u8; 32] = hasher.finalize().into(); - // TODO: store da in db - self.ops.push(ProtocolOperation::DaCommitment(hash.into())); + let commitment: Buf32 = hash.into(); + + self.ops.push(ProtocolOperation::DaCommitment(commitment)); + // Collect da + self.da_entries.push(DaEntry::new(commitment, blob)); } fn visit_deposit(&mut self, d: DepositInfo) { @@ -40,12 +51,23 @@ impl OpsVisitor for ClientOpsVisitor { } fn visit_deposit_request(&mut self, d: DepositRequestInfo) { - self.ops.push(ProtocolOperation::DepositRequest(d)); + self.ops.push(ProtocolOperation::DepositRequest(d.clone())); + self.deposit_requests.push(d); } fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } + + fn collect( + self, + ) -> ( + Vec, + Vec, + Vec, + ) { + (self.ops, self.deposit_requests, self.da_entries) + } } #[cfg(test)] @@ -56,7 +78,7 @@ mod test { Amount, Block, BlockHash, CompactTarget, ScriptBuf, Transaction, TxMerkleNode, }; use strata_l1tx::filter::{ - visitor::{BlockIndexer, DepositRequestIndexer, OpIndexer}, + indexer::{BlockIndexer, OpIndexer}, TxFilterConfig, }; use strata_primitives::{ @@ -73,7 +95,7 @@ mod test { ArbitraryGenerator, }; - use crate::{reader::ops_visitor::ClientOpsVisitor, test_utils::create_checkpoint_envelope_tx}; + use crate::{reader::tx_indexer::ClientTxIndexer, test_utils::create_checkpoint_envelope_tx}; const TEST_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; @@ -114,8 +136,8 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); @@ -158,10 +180,8 @@ mod test { let block = create_test_block(vec![tx]); - let deposit_req_indexer = DepositRequestIndexer::new(); - let dep_reqs = deposit_req_indexer - .index_block(&block, &filter_config) - .collect(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); + let (_, dep_reqs, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert_eq!(dep_reqs.len(), 1, "Should find one deposit request"); @@ -187,8 +207,8 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); - let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert!( tx_entries.is_empty(), @@ -222,8 +242,8 @@ mod test { let block = create_test_block(vec![tx1, tx2]); - let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); @@ -286,9 +306,8 @@ mod test { // Create a block with single tx that has multiple ops let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); - println!("TX ENTRIES: {:?}", tx_entries); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert_eq!( tx_entries.len(), diff --git a/crates/l1tx/src/filter/visitor.rs b/crates/l1tx/src/filter/indexer.rs similarity index 67% rename from crates/l1tx/src/filter/visitor.rs rename to crates/l1tx/src/filter/indexer.rs index 94c321c75..1013eb6d6 100644 --- a/crates/l1tx/src/filter/visitor.rs +++ b/crates/l1tx/src/filter/indexer.rs @@ -8,9 +8,9 @@ use super::{ parse_checkpoint_envelopes, parse_da_blobs, parse_deposit_requests, parse_deposits, TxFilterConfig, }; -use crate::messages::ProtocolTxEntry; +use crate::messages::{DaEntry, ProtocolTxEntry}; -pub trait OpsVisitor { +pub trait TxIndexer { // Do stuffs with `SignedBatchCheckpoint`. fn visit_checkpoint(&mut self, _chkpt: SignedBatchCheckpoint) {} @@ -23,14 +23,17 @@ pub trait OpsVisitor { // Do stuffs with DA. fn visit_da<'a>(&mut self, _d: impl Iterator) {} - fn collect(self) -> Vec; + // Collect data + fn collect( + self, + ) -> ( + Vec, + Vec, + Vec, + ); } pub trait BlockIndexer { - type Output; - - fn collect(self) -> Self::Output; - fn index_tx(&mut self, txidx: u32, tx: &Transaction, config: &TxFilterConfig); fn index_block(mut self, block: &Block, config: &TxFilterConfig) -> Self @@ -45,36 +48,29 @@ pub trait BlockIndexer { } #[derive(Clone, Debug)] -pub struct OpIndexer { +pub struct OpIndexer { visitor: V, tx_entries: Vec, + dep_reqs: Vec, + da_entries: Vec, } -impl OpIndexer { +impl OpIndexer { pub fn new(visitor: V) -> Self { Self { visitor, tx_entries: Vec::new(), + dep_reqs: Vec::new(), + da_entries: Vec::new(), } } -} - -#[derive(Clone, Default)] -pub struct DepositRequestIndexer { - requests: Vec, -} -impl DepositRequestIndexer { - pub fn new() -> Self { - Self { - ..Default::default() - } + pub fn collect(self) -> (Vec, Vec, Vec) { + (self.tx_entries, self.dep_reqs, self.da_entries) } } -impl BlockIndexer for OpIndexer { - type Output = Vec; - +impl BlockIndexer for OpIndexer { fn index_tx(&mut self, txidx: u32, tx: &Transaction, config: &TxFilterConfig) { let mut visitor = self.visitor.clone(); for chp in parse_checkpoint_envelopes(tx, config) { @@ -94,26 +90,13 @@ impl BlockIndexer for OpIndexer { visitor.visit_da(da); } - let ops = visitor.collect(); + let (ops, dep_reqs, da_entries) = visitor.collect(); if !ops.is_empty() { let entry = ProtocolTxEntry::new(txidx, ops); self.tx_entries.push(entry); } - } - - fn collect(self) -> Self::Output { - self.tx_entries - } -} - -impl BlockIndexer for DepositRequestIndexer { - type Output = Vec; - - fn collect(self) -> Self::Output { - self.requests - } - fn index_tx(&mut self, _txidx: u32, tx: &Transaction, config: &TxFilterConfig) { - self.requests.extend(parse_deposit_requests(tx, config)); + self.dep_reqs.extend_from_slice(&dep_reqs); + self.da_entries.extend_from_slice(&da_entries); } } diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index 123873025..d857cc29b 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -6,8 +6,8 @@ use strata_state::{ }; use tracing::warn; +pub mod indexer; pub mod types; -pub mod visitor; pub use types::TxFilterConfig; diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 25e57f147..9a2e4264d 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -1,5 +1,6 @@ use bitcoin::Block; use borsh::{BorshDeserialize, BorshSerialize}; +use strata_primitives::buf::Buf32; use strata_state::{l1::HeaderVerificationState, tx::ProtocolOperation}; /// L1 events that we observe and want the persistence task to work on. @@ -45,6 +46,21 @@ impl ProtocolTxEntry { } } +/// Da data retrieved from L1 transaction. +#[derive(Clone, Debug)] +pub struct DaEntry { + #[allow(unused)] + commitment: Buf32, + #[allow(unused)] + blob: Vec, +} + +impl DaEntry { + pub fn new(commitment: Buf32, blob: Vec) -> Self { + Self { commitment, blob } + } +} + /// Store the bitcoin block and references to the relevant transactions within the block #[derive(Clone, Debug)] pub struct BlockData { diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index da0068730..b5576471b 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -3,7 +3,7 @@ use bitcoin::Block; use strata_l1tx::filter::{ - visitor::{BlockIndexer, OpIndexer}, + indexer::{BlockIndexer, OpIndexer}, TxFilterConfig, }; use strata_primitives::{block_credential::CredRule, params::RollupParams}; @@ -12,7 +12,7 @@ use strata_state::{ tx::{DepositInfo, ProtocolOperation}, }; -use crate::ops_visitor::ProverOpsVisitor; +use crate::tx_indexer::ProverTxIndexer; pub fn extract_relevant_info( block: &Block, @@ -24,8 +24,8 @@ pub fn extract_relevant_info( // Just pass a no-op to the filter function as prover does not have to do anything with the raw // data like storing in db. - let indexer = OpIndexer::new(ProverOpsVisitor::new()); - let tx_refs = indexer.index_block(block, filter_config).collect(); + let indexer = OpIndexer::new(ProverTxIndexer::new()); + let (tx_refs, _, _) = indexer.index_block(block, filter_config).collect(); for op in tx_refs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { match op { diff --git a/crates/proof-impl/btc-blockspace/src/lib.rs b/crates/proof-impl/btc-blockspace/src/lib.rs index aec13ec9c..2090e17d1 100644 --- a/crates/proof-impl/btc-blockspace/src/lib.rs +++ b/crates/proof-impl/btc-blockspace/src/lib.rs @@ -4,7 +4,7 @@ pub mod block; pub mod filter; pub mod logic; pub mod merkle; -mod ops_visitor; pub mod prover; pub mod scan; pub mod tx; +mod tx_indexer; diff --git a/crates/proof-impl/btc-blockspace/src/ops_visitor.rs b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs similarity index 88% rename from crates/proof-impl/btc-blockspace/src/ops_visitor.rs rename to crates/proof-impl/btc-blockspace/src/tx_indexer.rs index 73978666e..5511d024f 100644 --- a/crates/proof-impl/btc-blockspace/src/ops_visitor.rs +++ b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs @@ -1,29 +1,25 @@ use digest::Digest; use sha2::Sha256; -use strata_l1tx::filter::visitor::OpsVisitor; +use strata_l1tx::filter::indexer::TxIndexer; use strata_state::{ batch::SignedBatchCheckpoint, tx::{DepositInfo, ProtocolOperation}, }; -/// Ops visitor for Prover. Basically this efficiently gets da commitment from chunks without doing +/// Ops indexer for Prover. Basically this efficiently gets da commitment from chunks without doing /// anything else with the chunks. #[derive(Debug, Clone)] -pub(crate) struct ProverOpsVisitor { +pub(crate) struct ProverTxIndexer { ops: Vec, } -impl ProverOpsVisitor { +impl ProverTxIndexer { pub fn new() -> Self { Self { ops: Vec::new() } } } -impl OpsVisitor for ProverOpsVisitor { - fn collect(self) -> Vec { - self.ops - } - +impl TxIndexer for ProverTxIndexer { fn visit_da<'a>(&mut self, chunks: impl Iterator) { let mut hasher = Sha256::new(); for chunk in chunks { @@ -40,6 +36,16 @@ impl OpsVisitor for ProverOpsVisitor { fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } + + fn collect( + self, + ) -> ( + Vec, + Vec, + Vec, + ) { + (self.ops, Vec::new(), Vec::new()) + } } /// These are mostly similar to the ones in `strata_btcio::reader::ops_visitor` except for the @@ -53,7 +59,7 @@ mod test { }; use strata_btcio::test_utils::create_checkpoint_envelope_tx; use strata_l1tx::filter::{ - visitor::{BlockIndexer, OpIndexer}, + indexer::{BlockIndexer, OpIndexer}, TxFilterConfig, }; use strata_primitives::{ @@ -67,7 +73,7 @@ mod test { ArbitraryGenerator, }; - use super::ProverOpsVisitor; + use super::ProverTxIndexer; const TEST_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; @@ -108,8 +114,8 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); @@ -140,8 +146,8 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); - let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert!( tx_entries.is_empty(), @@ -175,8 +181,8 @@ mod test { let block = create_test_block(vec![tx1, tx2]); - let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); @@ -239,9 +245,8 @@ mod test { // Create a block with single tx that has multiple ops let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ProverOpsVisitor::new()); - let tx_entries = ops_indexer.index_block(&block, &filter_config).collect(); - println!("TX ENTRIES: {:?}", tx_entries); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); + let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); assert_eq!( tx_entries.len(), diff --git a/crates/sequencer/src/block_template/block_assembly.rs b/crates/sequencer/src/block_template/block_assembly.rs index b43933810..05f49e180 100644 --- a/crates/sequencer/src/block_template/block_assembly.rs +++ b/crates/sequencer/src/block_template/block_assembly.rs @@ -196,7 +196,7 @@ fn fetch_deposit_update_txs(h: u64, l1man: &L1BlockManager) -> Result Date: Mon, 3 Feb 2025 16:01:40 +0545 Subject: [PATCH 18/22] Some restructuring of tx indexer return types --- crates/btcio/src/reader/tx_indexer.rs | 15 +++----- crates/l1tx/src/filter/indexer.rs | 34 ++++++++++------- crates/l1tx/src/messages.rs | 38 ++++++++++++++++++- .../btc-blockspace/src/tx_indexer.rs | 12 ++---- crates/state/src/state_op.rs | 4 +- 5 files changed, 68 insertions(+), 35 deletions(-) diff --git a/crates/btcio/src/reader/tx_indexer.rs b/crates/btcio/src/reader/tx_indexer.rs index 955a5830b..7af8c5546 100644 --- a/crates/btcio/src/reader/tx_indexer.rs +++ b/crates/btcio/src/reader/tx_indexer.rs @@ -1,6 +1,9 @@ use digest::Digest; use sha2::Sha256; -use strata_l1tx::{filter::indexer::TxIndexer, messages::DaEntry}; +use strata_l1tx::{ + filter::indexer::TxIndexer, + messages::{DaEntry, L1TxExtract}, +}; use strata_primitives::buf::Buf32; use strata_state::{ batch::SignedBatchCheckpoint, @@ -59,14 +62,8 @@ impl TxIndexer for ClientTxIndexer { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } - fn collect( - self, - ) -> ( - Vec, - Vec, - Vec, - ) { - (self.ops, self.deposit_requests, self.da_entries) + fn collect(self) -> L1TxExtract { + L1TxExtract::new(self.ops, self.deposit_requests, self.da_entries) } } diff --git a/crates/l1tx/src/filter/indexer.rs b/crates/l1tx/src/filter/indexer.rs index 1013eb6d6..dd6bfb3d0 100644 --- a/crates/l1tx/src/filter/indexer.rs +++ b/crates/l1tx/src/filter/indexer.rs @@ -1,14 +1,14 @@ use bitcoin::{Block, Transaction}; use strata_state::{ batch::SignedBatchCheckpoint, - tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, + tx::{DepositInfo, DepositRequestInfo}, }; use super::{ parse_checkpoint_envelopes, parse_da_blobs, parse_deposit_requests, parse_deposits, TxFilterConfig, }; -use crate::messages::{DaEntry, ProtocolTxEntry}; +use crate::messages::{DaEntry, L1TxExtract, ProtocolTxEntry}; pub trait TxIndexer { // Do stuffs with `SignedBatchCheckpoint`. @@ -24,13 +24,7 @@ pub trait TxIndexer { fn visit_da<'a>(&mut self, _d: impl Iterator) {} // Collect data - fn collect( - self, - ) -> ( - Vec, - Vec, - Vec, - ); + fn collect(self) -> L1TxExtract; } pub trait BlockIndexer { @@ -65,6 +59,18 @@ impl OpIndexer { } } + pub fn tx_entries(&self) -> &[ProtocolTxEntry] { + &self.tx_entries + } + + pub fn deposit_requests(&self) -> &[DepositRequestInfo] { + &self.dep_reqs + } + + pub fn da_entries(&self) -> &[DaEntry] { + &self.da_entries + } + pub fn collect(self) -> (Vec, Vec, Vec) { (self.tx_entries, self.dep_reqs, self.da_entries) } @@ -90,13 +96,13 @@ impl BlockIndexer for OpIndexer { visitor.visit_da(da); } - let (ops, dep_reqs, da_entries) = visitor.collect(); - if !ops.is_empty() { - let entry = ProtocolTxEntry::new(txidx, ops); + let tx_extract = visitor.collect(); + if !tx_extract.protocol_ops().is_empty() { + let entry = ProtocolTxEntry::new(txidx, tx_extract.protocol_ops().to_vec()); self.tx_entries.push(entry); } - self.dep_reqs.extend_from_slice(&dep_reqs); - self.da_entries.extend_from_slice(&da_entries); + self.dep_reqs.extend_from_slice(tx_extract.deposit_reqs()); + self.da_entries.extend_from_slice(tx_extract.da_entries()); } } diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 9a2e4264d..8384c2116 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -1,7 +1,10 @@ use bitcoin::Block; use borsh::{BorshDeserialize, BorshSerialize}; use strata_primitives::buf::Buf32; -use strata_state::{l1::HeaderVerificationState, tx::ProtocolOperation}; +use strata_state::{ + l1::HeaderVerificationState, + tx::{DepositRequestInfo, ProtocolOperation}, +}; /// L1 events that we observe and want the persistence task to work on. #[derive(Clone, Debug)] @@ -46,6 +49,39 @@ impl ProtocolTxEntry { } } +/// Consolidation of items extractable from an L1 Transaction. +pub struct L1TxExtract { + protocol_ops: Vec, + deposit_reqs: Vec, + da_entries: Vec, +} + +impl L1TxExtract { + pub fn new( + protocol_ops: Vec, + deposit_reqs: Vec, + da_entries: Vec, + ) -> Self { + Self { + protocol_ops, + deposit_reqs, + da_entries, + } + } + + pub fn protocol_ops(&self) -> &[ProtocolOperation] { + &self.protocol_ops + } + + pub fn deposit_reqs(&self) -> &[DepositRequestInfo] { + &self.deposit_reqs + } + + pub fn da_entries(&self) -> &[DaEntry] { + &self.da_entries + } +} + /// Da data retrieved from L1 transaction. #[derive(Clone, Debug)] pub struct DaEntry { diff --git a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs index 5511d024f..48c3330aa 100644 --- a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs +++ b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs @@ -1,6 +1,6 @@ use digest::Digest; use sha2::Sha256; -use strata_l1tx::filter::indexer::TxIndexer; +use strata_l1tx::{filter::indexer::TxIndexer, messages::L1TxExtract}; use strata_state::{ batch::SignedBatchCheckpoint, tx::{DepositInfo, ProtocolOperation}, @@ -37,14 +37,8 @@ impl TxIndexer for ProverTxIndexer { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } - fn collect( - self, - ) -> ( - Vec, - Vec, - Vec, - ) { - (self.ops, Vec::new(), Vec::new()) + fn collect(self) -> L1TxExtract { + L1TxExtract::new(self.ops, Vec::new(), Vec::new()) } } diff --git a/crates/state/src/state_op.rs b/crates/state/src/state_op.rs index 5c67cc3de..29da38ed7 100644 --- a/crates/state/src/state_op.rs +++ b/crates/state/src/state_op.rs @@ -231,8 +231,8 @@ impl StateCache { // TODO add it to the MMR so we can reference it in the future let (header_record, deposit_txs, _) = matured_block.into_parts(); - for tx in deposit_txs { - if let Deposit(deposit_info) = tx.tx().protocol_operation() { + for op in deposit_txs.iter().flat_map(|tx| tx.tx().protocol_ops()) { + if let Deposit(deposit_info) = op { trace!("we got some deposit_txs"); let amt = deposit_info.amt; let deposit_intent = DepositIntent::new(amt, &deposit_info.address); From 27fd672d00bd371c04baf90454b1bc0c05c379d3 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Tue, 4 Feb 2025 16:46:33 +0545 Subject: [PATCH 19/22] Some restructuring --- crates/btcio/src/reader/query.rs | 3 +- crates/btcio/src/reader/tx_indexer.rs | 25 ++++++++--- crates/l1tx/src/filter/indexer.rs | 13 +++--- crates/l1tx/src/messages.rs | 43 +++++++++++++++++++ .../proof-impl/btc-blockspace/src/filter.rs | 7 +-- .../btc-blockspace/src/tx_indexer.rs | 20 +++++++-- 6 files changed, 93 insertions(+), 18 deletions(-) diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index 66c61db19..cc269ee64 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -330,7 +330,8 @@ async fn process_block( let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); let (tx_entries, _dep_reqs, _da_entries) = ops_indexer .index_block(&block, state.filter_config()) - .collect(); + .collect() + .into_parts(); // TODO: do stuffs with dep_reqs and da_entries diff --git a/crates/btcio/src/reader/tx_indexer.rs b/crates/btcio/src/reader/tx_indexer.rs index 7af8c5546..2d0539bc5 100644 --- a/crates/btcio/src/reader/tx_indexer.rs +++ b/crates/btcio/src/reader/tx_indexer.rs @@ -134,7 +134,10 @@ mod test { let block = create_test_block(vec![tx]); let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); @@ -178,7 +181,10 @@ mod test { let block = create_test_block(vec![tx]); let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (_, dep_reqs, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (_, dep_reqs, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert_eq!(dep_reqs.len(), 1, "Should find one deposit request"); @@ -205,7 +211,10 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert!( tx_entries.is_empty(), @@ -240,7 +249,10 @@ mod test { let block = create_test_block(vec![tx1, tx2]); let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); @@ -304,7 +316,10 @@ mod test { let block = create_test_block(vec![tx]); let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert_eq!( tx_entries.len(), diff --git a/crates/l1tx/src/filter/indexer.rs b/crates/l1tx/src/filter/indexer.rs index dd6bfb3d0..c36b0cfb1 100644 --- a/crates/l1tx/src/filter/indexer.rs +++ b/crates/l1tx/src/filter/indexer.rs @@ -8,7 +8,7 @@ use super::{ parse_checkpoint_envelopes, parse_da_blobs, parse_deposit_requests, parse_deposits, TxFilterConfig, }; -use crate::messages::{DaEntry, L1TxExtract, ProtocolTxEntry}; +use crate::messages::{DaEntry, L1BlockExtract, L1TxExtract, ProtocolTxEntry}; pub trait TxIndexer { // Do stuffs with `SignedBatchCheckpoint`. @@ -39,6 +39,9 @@ pub trait BlockIndexer { } self } + + // Collect data + fn collect(self) -> L1BlockExtract; } #[derive(Clone, Debug)] @@ -70,10 +73,6 @@ impl OpIndexer { pub fn da_entries(&self) -> &[DaEntry] { &self.da_entries } - - pub fn collect(self) -> (Vec, Vec, Vec) { - (self.tx_entries, self.dep_reqs, self.da_entries) - } } impl BlockIndexer for OpIndexer { @@ -105,4 +104,8 @@ impl BlockIndexer for OpIndexer { self.dep_reqs.extend_from_slice(tx_extract.deposit_reqs()); self.da_entries.extend_from_slice(tx_extract.da_entries()); } + + fn collect(self) -> L1BlockExtract { + L1BlockExtract::new(self.tx_entries, self.dep_reqs, self.da_entries) + } } diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 8384c2116..dbebc93d6 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -51,8 +51,11 @@ impl ProtocolTxEntry { /// Consolidation of items extractable from an L1 Transaction. pub struct L1TxExtract { + // Protocol operations relevant to STF. protocol_ops: Vec, + // Deposit requests which the node stores for non-stf related bookkeeping. deposit_reqs: Vec, + // DA entries which the node stores for state reconstruction. da_entries: Vec, } @@ -82,6 +85,46 @@ impl L1TxExtract { } } +/// Consolidation of items extractable from an L1 Block. +pub struct L1BlockExtract { + // Transaction entries that contain protocol operations. + tx_entries: Vec, + // Deposit requests which the node stores for non-stf related bookkeeping. + deposit_reqs: Vec, + // DA entries which the node stores for state reconstruction. + da_entries: Vec, +} + +impl L1BlockExtract { + pub fn new( + tx_entries: Vec, + deposit_reqs: Vec, + da_entries: Vec, + ) -> Self { + Self { + tx_entries, + deposit_reqs, + da_entries, + } + } + + pub fn tx_entries(&self) -> &[ProtocolTxEntry] { + &self.tx_entries + } + + pub fn deposit_reqs(&self) -> &[DepositRequestInfo] { + &self.deposit_reqs + } + + pub fn da_entries(&self) -> &[DaEntry] { + &self.da_entries + } + + pub fn into_parts(self) -> (Vec, Vec, Vec) { + (self.tx_entries, self.deposit_reqs, self.da_entries) + } +} + /// Da data retrieved from L1 transaction. #[derive(Clone, Debug)] pub struct DaEntry { diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index b5576471b..c3babf476 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -22,10 +22,11 @@ pub fn extract_relevant_info( let mut deposits = Vec::new(); let mut prev_checkpoint = None; - // Just pass a no-op to the filter function as prover does not have to do anything with the raw - // data like storing in db. let indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_refs, _, _) = indexer.index_block(block, filter_config).collect(); + let (tx_refs, _, _) = indexer + .index_block(block, filter_config) + .collect() + .into_parts(); for op in tx_refs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { match op { diff --git a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs index 48c3330aa..da04b2ca0 100644 --- a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs +++ b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs @@ -109,7 +109,10 @@ mod test { let block = create_test_block(vec![tx]); let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); @@ -141,7 +144,10 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert!( tx_entries.is_empty(), @@ -176,7 +182,10 @@ mod test { let block = create_test_block(vec![tx1, tx2]); let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); @@ -240,7 +249,10 @@ mod test { let block = create_test_block(vec![tx]); let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer.index_block(&block, &filter_config).collect(); + let (tx_entries, _, _) = ops_indexer + .index_block(&block, &filter_config) + .collect() + .into_parts(); assert_eq!( tx_entries.len(), From 9af0885d27624f0152b53627dd0ce7437ab01481 Mon Sep 17 00:00:00 2001 From: Bibek Pandey Date: Wed, 5 Feb 2025 12:59:11 +0545 Subject: [PATCH 20/22] Some restructuring: move filter config inside OpIndexer --- crates/btcio/src/reader/query.rs | 12 ++-- crates/btcio/src/reader/tx_indexer.rs | 40 ++++------- crates/l1tx/src/filter/indexer.rs | 69 +++++++++++-------- crates/l1tx/src/messages.rs | 21 +++++- .../proof-impl/btc-blockspace/src/filter.rs | 7 +- .../btc-blockspace/src/tx_indexer.rs | 30 +++----- 6 files changed, 90 insertions(+), 89 deletions(-) diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index cc269ee64..eb1f3682a 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -159,9 +159,9 @@ async fn update_epoch_and_filter_config( state.set_epoch(new_epoch); // TODO: pass in chainstate to `derive_from` let new_config = TxFilterConfig::derive_from(ctx.params.rollup())?; - let curr_filter_config = state.filter_config().clone(); + let curr_filter_config = state.filter_config(); - if new_config != curr_filter_config { + if new_config != *curr_filter_config { state.set_filter_config(new_config.clone()); return Ok(Some(new_config)); } @@ -327,11 +327,9 @@ async fn process_block( let params = ctx.params.clone(); // Index ops - let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _dep_reqs, _da_entries) = ops_indexer - .index_block(&block, state.filter_config()) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), state.filter_config()); + let (tx_entries, _dep_reqs, _da_entries) = + ops_indexer.index_block(&block).finalize().into_parts(); // TODO: do stuffs with dep_reqs and da_entries diff --git a/crates/btcio/src/reader/tx_indexer.rs b/crates/btcio/src/reader/tx_indexer.rs index 2d0539bc5..3dcae9d2f 100644 --- a/crates/btcio/src/reader/tx_indexer.rs +++ b/crates/btcio/src/reader/tx_indexer.rs @@ -46,7 +46,8 @@ impl TxIndexer for ClientTxIndexer { self.ops.push(ProtocolOperation::DaCommitment(commitment)); // Collect da - self.da_entries.push(DaEntry::new(commitment, blob)); + self.da_entries + .push(DaEntry::new_unchecked(commitment, blob)); } fn visit_deposit(&mut self, d: DepositInfo) { @@ -62,7 +63,7 @@ impl TxIndexer for ClientTxIndexer { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } - fn collect(self) -> L1TxExtract { + fn finalize(self) -> L1TxExtract { L1TxExtract::new(self.ops, self.deposit_requests, self.da_entries) } } @@ -133,11 +134,8 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); @@ -180,11 +178,8 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (_, dep_reqs, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); + let (_, dep_reqs, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert_eq!(dep_reqs.len(), 1, "Should find one deposit request"); @@ -210,11 +205,8 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert!( tx_entries.is_empty(), @@ -248,11 +240,8 @@ mod test { let block = create_test_block(vec![tx1, tx2]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); @@ -315,11 +304,8 @@ mod test { // Create a block with single tx that has multiple ops let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert_eq!( tx_entries.len(), diff --git a/crates/l1tx/src/filter/indexer.rs b/crates/l1tx/src/filter/indexer.rs index c36b0cfb1..c2a942ae6 100644 --- a/crates/l1tx/src/filter/indexer.rs +++ b/crates/l1tx/src/filter/indexer.rs @@ -10,6 +10,7 @@ use super::{ }; use crate::messages::{DaEntry, L1BlockExtract, L1TxExtract, ProtocolTxEntry}; +/// Interface to extract relevant information from a transaction. pub trait TxIndexer { // Do stuffs with `SignedBatchCheckpoint`. fn visit_checkpoint(&mut self, _chkpt: SignedBatchCheckpoint) {} @@ -24,38 +25,51 @@ pub trait TxIndexer { fn visit_da<'a>(&mut self, _d: impl Iterator) {} // Collect data - fn collect(self) -> L1TxExtract; + fn finalize(self) -> L1TxExtract; } +/// Interface to extract relevant information from a block. pub trait BlockIndexer { - fn index_tx(&mut self, txidx: u32, tx: &Transaction, config: &TxFilterConfig); + fn index_tx(&mut self, txidx: u32, tx: &Transaction); - fn index_block(mut self, block: &Block, config: &TxFilterConfig) -> Self + fn index_block(mut self, block: &Block) -> Self where Self: Sized, { for (i, tx) in block.txdata.iter().enumerate() { - self.index_tx(i as u32, tx, config); + self.index_tx(i as u32, tx); } self } // Collect data - fn collect(self) -> L1BlockExtract; + fn finalize(self) -> L1BlockExtract; } +/// Indexes `ProtocolTxEntry`s, `DepositRequestInfo`s and `DaEntry`s from a bitcoin block. +/// Currently, this is used from two contexts: rollup node and prover node, each of which will have +/// different `TxIndexer`s which determine what and how something is extracted from a transaction. #[derive(Clone, Debug)] -pub struct OpIndexer { - visitor: V, +pub struct OpIndexer<'a, T: TxIndexer> { + /// The actual logic of what and how something is extracted from a transaction. + tx_indexer: T, + /// The config that's used to filter transactions and extract data. This has a lifetime + /// parameter for two reasons: 1) It is used in prover context so using Arc might incur some + /// overheads, 2) We can be sure that the config won't change during indexing of a l1 block. + filter_config: &'a TxFilterConfig, + /// `ProtocolTxEntry`s will be accumulated here. tx_entries: Vec, + /// `DepositRequestInfo`s will be accumulated here. dep_reqs: Vec, + /// `DaEntry`s will be accumulated here. da_entries: Vec, } -impl OpIndexer { - pub fn new(visitor: V) -> Self { +impl<'a, T: TxIndexer> OpIndexer<'a, T> { + pub fn new(tx_indexer: T, filter_config: &'a TxFilterConfig) -> Self { Self { - visitor, + tx_indexer, + filter_config, tx_entries: Vec::new(), dep_reqs: Vec::new(), da_entries: Vec::new(), @@ -75,37 +89,38 @@ impl OpIndexer { } } -impl BlockIndexer for OpIndexer { - fn index_tx(&mut self, txidx: u32, tx: &Transaction, config: &TxFilterConfig) { - let mut visitor = self.visitor.clone(); - for chp in parse_checkpoint_envelopes(tx, config) { - visitor.visit_checkpoint(chp); +impl BlockIndexer for OpIndexer<'_, V> { + fn index_tx(&mut self, txidx: u32, tx: &Transaction) { + let mut tx_indexer = self.tx_indexer.clone(); + for chp in parse_checkpoint_envelopes(tx, self.filter_config) { + tx_indexer.visit_checkpoint(chp); } - for dp in parse_deposits(tx, config) { - visitor.visit_deposit(dp); + for dp in parse_deposits(tx, self.filter_config) { + tx_indexer.visit_deposit(dp); } // TODO: remove this later when we do not require deposit request ops - for dp in parse_deposit_requests(tx, config) { - visitor.visit_deposit_request(dp); + for dp in parse_deposit_requests(tx, self.filter_config) { + tx_indexer.visit_deposit_request(dp); } - for da in parse_da_blobs(tx, config) { - visitor.visit_da(da); + for da in parse_da_blobs(tx, self.filter_config) { + tx_indexer.visit_da(da); } - let tx_extract = visitor.collect(); - if !tx_extract.protocol_ops().is_empty() { - let entry = ProtocolTxEntry::new(txidx, tx_extract.protocol_ops().to_vec()); + let tx_extract = tx_indexer.finalize(); + let (ops, mut deps, mut das) = tx_extract.into_parts(); + if !ops.is_empty() { + let entry = ProtocolTxEntry::new(txidx, ops); self.tx_entries.push(entry); } - self.dep_reqs.extend_from_slice(tx_extract.deposit_reqs()); - self.da_entries.extend_from_slice(tx_extract.da_entries()); + self.dep_reqs.append(&mut deps); + self.da_entries.append(&mut das); } - fn collect(self) -> L1BlockExtract { + fn finalize(self) -> L1BlockExtract { L1BlockExtract::new(self.tx_entries, self.dep_reqs, self.da_entries) } } diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index dbebc93d6..44df366d5 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -1,6 +1,6 @@ use bitcoin::Block; use borsh::{BorshDeserialize, BorshSerialize}; -use strata_primitives::buf::Buf32; +use strata_primitives::{buf::Buf32, hash}; use strata_state::{ l1::HeaderVerificationState, tx::{DepositRequestInfo, ProtocolOperation}, @@ -83,6 +83,16 @@ impl L1TxExtract { pub fn da_entries(&self) -> &[DaEntry] { &self.da_entries } + + pub fn into_parts( + self, + ) -> ( + Vec, + Vec, + Vec, + ) { + (self.protocol_ops, self.deposit_reqs, self.da_entries) + } } /// Consolidation of items extractable from an L1 Block. @@ -135,7 +145,14 @@ pub struct DaEntry { } impl DaEntry { - pub fn new(commitment: Buf32, blob: Vec) -> Self { + /// Creates a new `DaEntry` instance that doesn't check that the commitment actually corresponds + /// to the blob. + pub fn new_unchecked(commitment: Buf32, blob: Vec) -> Self { + Self { commitment, blob } + } + + pub fn new(blob: Vec) -> Self { + let commitment = hash::raw(&blob); Self { commitment, blob } } } diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index c3babf476..ffc9e8b88 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -22,11 +22,8 @@ pub fn extract_relevant_info( let mut deposits = Vec::new(); let mut prev_checkpoint = None; - let indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_refs, _, _) = indexer - .index_block(block, filter_config) - .collect() - .into_parts(); + let indexer = OpIndexer::new(ProverTxIndexer::new(), filter_config); + let (tx_refs, _, _) = indexer.index_block(block).finalize().into_parts(); for op in tx_refs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { match op { diff --git a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs index da04b2ca0..1fd1f4eab 100644 --- a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs +++ b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs @@ -37,7 +37,7 @@ impl TxIndexer for ProverTxIndexer { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } - fn collect(self) -> L1TxExtract { + fn finalize(self) -> L1TxExtract { L1TxExtract::new(self.ops, Vec::new(), Vec::new()) } } @@ -108,11 +108,8 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); @@ -143,11 +140,8 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert!( tx_entries.is_empty(), @@ -181,11 +175,8 @@ mod test { let block = create_test_block(vec![tx1, tx2]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); @@ -248,11 +239,8 @@ mod test { // Create a block with single tx that has multiple ops let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new()); - let (tx_entries, _, _) = ops_indexer - .index_block(&block, &filter_config) - .collect() - .into_parts(); + let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); + let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); assert_eq!( tx_entries.len(), From a9391fdca5e65d19405c091984dbd5ad9c9e4a3b Mon Sep 17 00:00:00 2001 From: Trey Del Bonis Date: Wed, 5 Feb 2025 22:56:13 -0500 Subject: [PATCH 21/22] misc: refactored tx indexing interface to be cleaner and more general --- crates/btcio/src/reader/query.rs | 23 +-- crates/btcio/src/reader/tx_indexer.rs | 87 +++++---- crates/consensus-logic/src/l1_handler.rs | 23 +-- crates/l1tx/src/filter/indexer.rs | 168 +++++++++++------ crates/l1tx/src/messages.rs | 170 ++++++++++-------- .../proof-impl/btc-blockspace/src/filter.rs | 14 +- .../btc-blockspace/src/tx_indexer.rs | 73 ++++---- crates/state/src/tx.rs | 66 ++++++- 8 files changed, 376 insertions(+), 248 deletions(-) diff --git a/crates/btcio/src/reader/query.rs b/crates/btcio/src/reader/query.rs index eb1f3682a..29707fd76 100644 --- a/crates/btcio/src/reader/query.rs +++ b/crates/btcio/src/reader/query.rs @@ -8,11 +8,8 @@ use anyhow::bail; use bitcoin::{Block, BlockHash}; use strata_config::btcio::ReaderConfig; use strata_l1tx::{ - filter::{ - indexer::{BlockIndexer, OpIndexer}, - TxFilterConfig, - }, - messages::{BlockData, L1Event}, + filter::{indexer::index_block, TxFilterConfig}, + messages::{BlockData, L1Event, RelevantTxEntry}, }; use strata_primitives::params::Params; use strata_state::l1::{ @@ -24,7 +21,7 @@ use tokio::sync::mpsc; use tracing::*; use crate::{ - reader::{state::ReaderState, tx_indexer::ClientTxIndexer}, + reader::{state::ReaderState, tx_indexer::ReaderTxVisitorImpl}, rpc::traits::ReaderRpc, status::{apply_status_updates, L1StatusUpdate}, }; @@ -33,12 +30,16 @@ use crate::{ struct ReaderContext { /// Bitcoin reader client client: Arc, + /// L1Event sender event_tx: mpsc::Sender, + /// Config config: Arc, + /// Params params: Arc, + /// Status transmitter status_channel: StatusChannel, } @@ -141,6 +142,7 @@ async fn handle_new_filter_rule( if ctx.event_tx.send(revert_ev).await.is_err() { warn!("unable to submit L1 reorg event, did persistence task exit?"); } + Ok(()) } @@ -326,14 +328,13 @@ async fn process_block( let txs = block.txdata.len(); let params = ctx.params.clone(); - // Index ops - let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), state.filter_config()); - let (tx_entries, _dep_reqs, _da_entries) = - ops_indexer.index_block(&block).finalize().into_parts(); + // Index all the stuff in the block. + let entries: Vec = + index_block(&block, ReaderTxVisitorImpl::new, state.filter_config()); // TODO: do stuffs with dep_reqs and da_entries - let block_data = BlockData::new(height, block, tx_entries); + let block_data = BlockData::new(height, block, entries); let l1blkid = block_data.block().block_hash(); trace!(%height, %l1blkid, %txs, "fetched block from client"); diff --git a/crates/btcio/src/reader/tx_indexer.rs b/crates/btcio/src/reader/tx_indexer.rs index 3dcae9d2f..07e541e88 100644 --- a/crates/btcio/src/reader/tx_indexer.rs +++ b/crates/btcio/src/reader/tx_indexer.rs @@ -1,10 +1,7 @@ -use digest::Digest; -use sha2::Sha256; use strata_l1tx::{ - filter::indexer::TxIndexer, - messages::{DaEntry, L1TxExtract}, + filter::indexer::TxVisitor, + messages::{DaEntry, L1TxMessages}, }; -use strata_primitives::buf::Buf32; use strata_state::{ batch::SignedBatchCheckpoint, tx::{DepositInfo, DepositRequestInfo, ProtocolOperation}, @@ -12,13 +9,13 @@ use strata_state::{ /// Ops indexer for rollup client. Collects extra info like da blobs and deposit requests #[derive(Clone, Debug)] -pub struct ClientTxIndexer { +pub struct ReaderTxVisitorImpl { ops: Vec, deposit_requests: Vec, da_entries: Vec, } -impl ClientTxIndexer { +impl ReaderTxVisitorImpl { pub fn new() -> Self { Self { ops: Vec::new(), @@ -32,39 +29,39 @@ impl ClientTxIndexer { } } -impl TxIndexer for ClientTxIndexer { - fn visit_da<'a>(&mut self, chunks: impl Iterator) { - let mut hasher = Sha256::new(); - let mut blob = Vec::new(); - - for chunk in chunks { - hasher.update(chunk); - blob.extend_from_slice(chunk); - } - let hash: [u8; 32] = hasher.finalize().into(); - let commitment: Buf32 = hash.into(); +impl TxVisitor for ReaderTxVisitorImpl { + type Output = L1TxMessages; - self.ops.push(ProtocolOperation::DaCommitment(commitment)); - // Collect da - self.da_entries - .push(DaEntry::new_unchecked(commitment, blob)); + fn visit_da<'a>(&mut self, chunks: impl Iterator) { + let da_entry = DaEntry::from_chunks(chunks); + self.ops + .push(ProtocolOperation::DaCommitment(*da_entry.commitment())); + self.da_entries.push(da_entry); } fn visit_deposit(&mut self, d: DepositInfo) { self.ops.push(ProtocolOperation::Deposit(d)); } - fn visit_deposit_request(&mut self, d: DepositRequestInfo) { - self.ops.push(ProtocolOperation::DepositRequest(d.clone())); - self.deposit_requests.push(d); + fn visit_deposit_request(&mut self, dr: DepositRequestInfo) { + self.ops.push(ProtocolOperation::DepositRequest(dr.clone())); + self.deposit_requests.push(dr); } fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { self.ops.push(ProtocolOperation::Checkpoint(chkpt)); } - fn finalize(self) -> L1TxExtract { - L1TxExtract::new(self.ops, self.deposit_requests, self.da_entries) + fn finalize(self) -> Option { + if self.ops.is_empty() && self.deposit_requests.is_empty() && self.da_entries.is_empty() { + None + } else { + Some(L1TxMessages::new( + self.ops, + self.deposit_requests, + self.da_entries, + )) + } } } @@ -75,10 +72,7 @@ mod test { hashes::Hash, Amount, Block, BlockHash, CompactTarget, ScriptBuf, Transaction, TxMerkleNode, }; - use strata_l1tx::filter::{ - indexer::{BlockIndexer, OpIndexer}, - TxFilterConfig, - }; + use strata_l1tx::filter::{indexer::index_block, TxFilterConfig}; use strata_primitives::{ l1::{payload::L1Payload, BitcoinAmount}, params::Params, @@ -93,7 +87,9 @@ mod test { ArbitraryGenerator, }; - use crate::{reader::tx_indexer::ClientTxIndexer, test_utils::create_checkpoint_envelope_tx}; + use crate::{ + reader::tx_indexer::ReaderTxVisitorImpl, test_utils::create_checkpoint_envelope_tx, + }; const TEST_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; @@ -134,12 +130,11 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ReaderTxVisitorImpl::new, &filter_config); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); - for op in tx_entries[0].proto_ops() { + for op in tx_entries[0].contents().protocol_ops() { if let ProtocolOperation::Deposit(deposit_info) = op { assert_eq!(deposit_info.address, ee_addr, "EE address should match"); assert_eq!( @@ -178,8 +173,11 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); - let (_, dep_reqs, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ReaderTxVisitorImpl::new, &filter_config); + let dep_reqs = tx_entries + .iter() + .flat_map(|tx| tx.contents().deposit_reqs()) + .collect::>(); assert_eq!(dep_reqs.len(), 1, "Should find one deposit request"); @@ -205,8 +203,7 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ReaderTxVisitorImpl::new, &filter_config); assert!( tx_entries.is_empty(), @@ -240,14 +237,13 @@ mod test { let block = create_test_block(vec![tx1, tx2]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ReaderTxVisitorImpl::new, &filter_config); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); for (i, info) in tx_entries .iter() - .flat_map(|op_txs| op_txs.proto_ops()) + .flat_map(|op_txs| op_txs.contents().protocol_ops()) .enumerate() { if let ProtocolOperation::Deposit(deposit_info) = info { @@ -304,8 +300,7 @@ mod test { // Create a block with single tx that has multiple ops let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ClientTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ReaderTxVisitorImpl::new, &filter_config); assert_eq!( tx_entries.len(), @@ -313,14 +308,14 @@ mod test { "Should find one matching transaction entry" ); assert_eq!( - tx_entries[0].proto_ops().len(), + tx_entries[0].contents().protocol_ops().len(), 2, "Should find two protocol ops" ); let mut dep_count = 0; let mut ckpt_count = 0; - for op in tx_entries[0].proto_ops() { + for op in tx_entries[0].contents().protocol_ops() { match op { ProtocolOperation::Deposit(_) => dep_count += 1, ProtocolOperation::Checkpoint(_) => ckpt_count += 1, diff --git a/crates/consensus-logic/src/l1_handler.rs b/crates/consensus-logic/src/l1_handler.rs index 7a2f4df54..91da7aba9 100644 --- a/crates/consensus-logic/src/l1_handler.rs +++ b/crates/consensus-logic/src/l1_handler.rs @@ -129,15 +129,18 @@ fn check_for_da_batch( blockdata: &BlockData, seq_pubkey: Option, ) -> Vec { - let protocol_ops_txs = blockdata.protocol_txs(); + let protocol_ops_txs = blockdata.relevant_txs(); - let signed_checkpts = protocol_ops_txs.iter().flat_map(|txref| { - txref.proto_ops().iter().filter_map(|op| match op { - ProtocolOperation::Checkpoint(envelope) => { - Some((envelope, &blockdata.block().txdata[txref.index() as usize])) - } - _ => None, - }) + let signed_checkpts = protocol_ops_txs.iter().flat_map(|tx| { + tx.contents() + .protocol_ops() + .iter() + .filter_map(|op| match op { + ProtocolOperation::Checkpoint(envelope) => { + Some((envelope, &blockdata.block().txdata[tx.index() as usize])) + } + _ => None, + }) }); let sig_verified_checkpoints = signed_checkpts.filter_map(|(signed_checkpoint, tx)| { @@ -239,13 +242,13 @@ fn generate_block_manifest(block: &Block, epoch: u64) -> L1BlockManifest { fn generate_l1txs(blockdata: &BlockData) -> Vec { blockdata - .protocol_txs() + .relevant_txs() .iter() .map(|ops_txs| { generate_l1_tx( blockdata.block(), ops_txs.index(), - ops_txs.proto_ops().to_vec(), + ops_txs.contents().protocol_ops().to_vec(), ) }) .collect() diff --git a/crates/l1tx/src/filter/indexer.rs b/crates/l1tx/src/filter/indexer.rs index c2a942ae6..107b053fb 100644 --- a/crates/l1tx/src/filter/indexer.rs +++ b/crates/l1tx/src/filter/indexer.rs @@ -8,76 +8,111 @@ use super::{ parse_checkpoint_envelopes, parse_da_blobs, parse_deposit_requests, parse_deposits, TxFilterConfig, }; -use crate::messages::{DaEntry, L1BlockExtract, L1TxExtract, ProtocolTxEntry}; +use crate::messages::IndexedTxEntry; -/// Interface to extract relevant information from a transaction. -pub trait TxIndexer { - // Do stuffs with `SignedBatchCheckpoint`. +/// Interface to handle storage of extracted information from a transaction. +pub trait TxVisitor { + /// Output type collecting what we want to extract from a tx. + type Output; + + /// Do stuffs with `SignedBatchCheckpoint`. fn visit_checkpoint(&mut self, _chkpt: SignedBatchCheckpoint) {} - // Do stuffs with `DepositInfo`. + /// Do stuffs with `DepositInfo`. fn visit_deposit(&mut self, _d: DepositInfo) {} - // Do stuffs with `DepositRequest`. + /// Do stuffs with `DepositRequest`. fn visit_deposit_request(&mut self, _d: DepositRequestInfo) {} - // Do stuffs with DA. + /// Do stuffs with DA. fn visit_da<'a>(&mut self, _d: impl Iterator) {} - // Collect data - fn finalize(self) -> L1TxExtract; + /// Export the indexed data, if it rose to the level of being useful. + fn finalize(self) -> Option; } -/// Interface to extract relevant information from a block. -pub trait BlockIndexer { - fn index_tx(&mut self, txidx: u32, tx: &Transaction); +/// Extracts a list of interesting transactions from a block according to a +/// provided visitor, with parts extracted from a provided filter config. +pub fn index_block( + block: &Block, + visitor_fn: impl Fn() -> V, + config: &TxFilterConfig, +) -> Vec> { + block + .txdata + .iter() + .enumerate() + .filter_map(|(i, tx)| { + index_tx(tx, visitor_fn(), config).map(|outp| IndexedTxEntry::new(i as u32, outp)) + }) + .collect::>() +} - fn index_block(mut self, block: &Block) -> Self - where - Self: Sized, - { - for (i, tx) in block.txdata.iter().enumerate() { - self.index_tx(i as u32, tx); - } - self +fn index_tx( + tx: &Transaction, + mut visitor: V, + filter_config: &TxFilterConfig, +) -> Option { + for ckpt in parse_checkpoint_envelopes(tx, filter_config) { + visitor.visit_checkpoint(ckpt); } - // Collect data - fn finalize(self) -> L1BlockExtract; + for dp in parse_deposits(tx, filter_config) { + visitor.visit_deposit(dp); + } + + for da in parse_da_blobs(tx, filter_config) { + visitor.visit_da(da); + } + + // TODO: maybe remove this later when we do not require deposit request ops? + for dr in parse_deposit_requests(tx, filter_config) { + visitor.visit_deposit_request(dr); + } + + visitor.finalize() +} + +/* +/// Interface to extract relevant information from a block. +pub trait BlockIndexer { + /// Output from the indexing pass. + type Output; + + /// Indexes the block and produces the output. + fn index_block(&self, block: &Block) -> Self::Output; } /// Indexes `ProtocolTxEntry`s, `DepositRequestInfo`s and `DaEntry`s from a bitcoin block. +/// /// Currently, this is used from two contexts: rollup node and prover node, each of which will have /// different `TxIndexer`s which determine what and how something is extracted from a transaction. -#[derive(Clone, Debug)] -pub struct OpIndexer<'a, T: TxIndexer> { +pub struct TxOpIndexer<'a, T: TxVisitor, F: Fn() -> T> { /// The actual logic of what and how something is extracted from a transaction. - tx_indexer: T, + tx_indexer_fn: F, + /// The config that's used to filter transactions and extract data. This has a lifetime /// parameter for two reasons: 1) It is used in prover context so using Arc might incur some /// overheads, 2) We can be sure that the config won't change during indexing of a l1 block. filter_config: &'a TxFilterConfig, + /// `ProtocolTxEntry`s will be accumulated here. - tx_entries: Vec, - /// `DepositRequestInfo`s will be accumulated here. - dep_reqs: Vec, - /// `DaEntry`s will be accumulated here. - da_entries: Vec, + relevant_txs: Vec>, } -impl<'a, T: TxIndexer> OpIndexer<'a, T> { - pub fn new(tx_indexer: T, filter_config: &'a TxFilterConfig) -> Self { +impl<'a, T: TxVisitor, F: Fn() -> T> TxOpIndexer<'a, T, F> { + pub fn new(tx_indexer_fn: F, filter_config: &'a TxFilterConfig) -> Self { Self { - tx_indexer, + tx_indexer_fn, filter_config, - tx_entries: Vec::new(), + relevant_txs: Vec::new(), dep_reqs: Vec::new(), da_entries: Vec::new(), } } - pub fn tx_entries(&self) -> &[ProtocolTxEntry] { - &self.tx_entries + pub fn tx_entries(&self) -> &[IndexedTxEntry] { + &self.relevant_txs } pub fn deposit_requests(&self) -> &[DepositRequestInfo] { @@ -89,38 +124,69 @@ impl<'a, T: TxIndexer> OpIndexer<'a, T> { } } -impl BlockIndexer for OpIndexer<'_, V> { - fn index_tx(&mut self, txidx: u32, tx: &Transaction) { - let mut tx_indexer = self.tx_indexer.clone(); - for chp in parse_checkpoint_envelopes(tx, self.filter_config) { - tx_indexer.visit_checkpoint(chp); +impl T> TxOpIndexer<'_, T, F> { + fn index_tx(&self, txidx: u32, tx: &Transaction) { + let mut tx_visitor = (self.tx_indexer_fn)(); + + for ckpt in parse_checkpoint_envelopes(tx, self.filter_config) { + tx_visitor.visit_checkpoint(ckpt); } for dp in parse_deposits(tx, self.filter_config) { - tx_indexer.visit_deposit(dp); + tx_visitor.visit_deposit(dp); } // TODO: remove this later when we do not require deposit request ops - for dp in parse_deposit_requests(tx, self.filter_config) { - tx_indexer.visit_deposit_request(dp); + for dr in parse_deposit_requests(tx, self.filter_config) { + tx_visitor.visit_deposit_request(dr); } for da in parse_da_blobs(tx, self.filter_config) { - tx_indexer.visit_da(da); + tx_visitor.visit_da(da); } - let tx_extract = tx_indexer.finalize(); - let (ops, mut deps, mut das) = tx_extract.into_parts(); - if !ops.is_empty() { - let entry = ProtocolTxEntry::new(txidx, ops); - self.tx_entries.push(entry); + // Finalize the visitor. If there's nothing to report then return + // immeditately. + if let Some(summary) = tx_visitor.finalize() { + self.relevant_txs.push(IndexedTxEntry::new(txidx, summary)); } self.dep_reqs.append(&mut deps); self.da_entries.append(&mut das); } +} + +impl T> BlockIndexer for TxOpIndexer<'_, T, F> { + type Output = Vec; + + fn index_block(&self, block: &Block) -> Self::Output + where + Self: Sized, + { + let mut op_txs = Vec::new(); + let mut deposit_reqs = Vec::new(); + let mut da_entries = Vec::new(); - fn finalize(self) -> L1BlockExtract { + for (i, tx) in block.txdata.iter().enumerate() { + self.index_tx(i as u32, tx); + } + + // TODO + } + + /*fn finalize(self) -> L1BlockExtract { L1BlockExtract::new(self.tx_entries, self.dep_reqs, self.da_entries) + }*/ +}*/ + +/// Generic no-op tx indexer that emits nothing for every tx but could +/// substitute for any type of visitor. +pub struct NopTxVisitorImpl(::std::marker::PhantomData); + +impl TxVisitor for NopTxVisitorImpl { + type Output = T; + + fn finalize(self) -> Option { + None } } diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 44df366d5..13ec95103 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -1,9 +1,8 @@ use bitcoin::Block; use borsh::{BorshDeserialize, BorshSerialize}; -use strata_primitives::{buf::Buf32, hash}; use strata_state::{ l1::HeaderVerificationState, - tx::{DepositRequestInfo, ProtocolOperation}, + tx::{DaCommitment, DepositRequestInfo, ProtocolOperation}, }; /// L1 events that we observe and want the persistence task to work on. @@ -22,44 +21,60 @@ pub enum L1Event { GenesisVerificationState(u64, HeaderVerificationState), } -/// Core protocol specific bitcoin transaction reference. A bitcoin transaction can have multiple -/// operations relevant to protocol. This is used in the context of [`BlockData`]. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] -pub struct ProtocolTxEntry { +/// Indexed transaction entry taken from +#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)] +pub struct IndexedTxEntry { /// Index of the transaction in the block index: u32, - /// The operation that is to be applied on data - proto_ops: Vec, + + /// Contents emitted from the visitor that was ran on this tx. + /// + /// This is probably a list of protocol operations. + contents: T, } -impl ProtocolTxEntry { - /// Creates a new [`ProtocolTxEntry`] - pub fn new(index: u32, proto_ops: Vec) -> Self { - Self { index, proto_ops } +impl IndexedTxEntry { + /// Creates a new instance. + pub fn new(index: u32, contents: T) -> Self { + Self { index, contents } } - /// Returns the index of the transaction + /// Returns the position of the transaction within the block. pub fn index(&self) -> u32 { self.index } - /// Returns a reference to the protocol operation - pub fn proto_ops(&self) -> &[ProtocolOperation] { - &self.proto_ops + /// Returns a reference to the contents. + pub fn contents(&self) -> &T { + &self.contents + } + + /// "Unwraps" the entry into its contents. + pub fn into_contents(self) -> T { + self.contents } } -/// Consolidation of items extractable from an L1 Transaction. -pub struct L1TxExtract { - // Protocol operations relevant to STF. +/* + * Core protocol specific bitcoin transaction reference. A bitcoin transaction can have multiple + * operations relevant to protocol. This is used in the context of [`BlockData`]. + */ + +/// Container for the different kinds of messages that we could extract from a L1 tx. +#[derive(Clone, Debug)] +pub struct L1TxMessages { + /// Protocol consensus operations relevant to STF. protocol_ops: Vec, - // Deposit requests which the node stores for non-stf related bookkeeping. + + /// Deposit requests which the node stores for non-stf related bookkeeping. deposit_reqs: Vec, - // DA entries which the node stores for state reconstruction. + + /// DA entries which the node stores for state reconstruction. These MUST + /// reflect messages found in `ProtocolOperation`. da_entries: Vec, } -impl L1TxExtract { +impl L1TxMessages { pub fn new( protocol_ops: Vec, deposit_reqs: Vec, @@ -95,99 +110,98 @@ impl L1TxExtract { } } -/// Consolidation of items extractable from an L1 Block. -pub struct L1BlockExtract { - // Transaction entries that contain protocol operations. - tx_entries: Vec, - // Deposit requests which the node stores for non-stf related bookkeeping. - deposit_reqs: Vec, - // DA entries which the node stores for state reconstruction. - da_entries: Vec, +/// Da data retrieved from L1 transaction. +#[derive(Clone, Debug)] +pub struct DaEntry { + #[allow(unused)] + commitment: DaCommitment, + + #[allow(unused)] + blob_buf: Vec, } -impl L1BlockExtract { - pub fn new( - tx_entries: Vec, - deposit_reqs: Vec, - da_entries: Vec, - ) -> Self { +impl DaEntry { + /// Creates a new `DaEntry` instance without checking that the commitment + /// actually corresponds to the blob. + pub fn new_unchecked(commitment: DaCommitment, blob_buf: Vec) -> Self { Self { - tx_entries, - deposit_reqs, - da_entries, + commitment, + blob_buf, } } - pub fn tx_entries(&self) -> &[ProtocolTxEntry] { - &self.tx_entries + /// Creates a new instance for a blob, generating the commitment. + pub fn new(blob: Vec) -> Self { + let commitment = DaCommitment::from_buf(&blob); + Self::new_unchecked(commitment, blob) } - pub fn deposit_reqs(&self) -> &[DepositRequestInfo] { - &self.deposit_reqs - } + /// Creates a new instance from an iterator over contiguous chunks of bytes. + /// + /// This is intended to be used when extracting data from an in-situ bitcoin + /// tx, which has a requirement that data is in 520 byte chunks. + pub fn from_chunks<'a>(chunks: impl Iterator) -> Self { + // I'm not sure if I can just like `.flatten().copied().collect()` this + // efficiently how it looks like you can. + let mut buf = Vec::new(); + chunks.for_each(|chunk| buf.extend_from_slice(chunk)); - pub fn da_entries(&self) -> &[DaEntry] { - &self.da_entries + Self::new(buf) } - pub fn into_parts(self) -> (Vec, Vec, Vec) { - (self.tx_entries, self.deposit_reqs, self.da_entries) + pub fn commitment(&self) -> &DaCommitment { + &self.commitment } -} -/// Da data retrieved from L1 transaction. -#[derive(Clone, Debug)] -pub struct DaEntry { - #[allow(unused)] - commitment: Buf32, - #[allow(unused)] - blob: Vec, -} - -impl DaEntry { - /// Creates a new `DaEntry` instance that doesn't check that the commitment actually corresponds - /// to the blob. - pub fn new_unchecked(commitment: Buf32, blob: Vec) -> Self { - Self { commitment, blob } + pub fn blob_buf(&self) -> &[u8] { + &self.blob_buf } - pub fn new(blob: Vec) -> Self { - let commitment = hash::raw(&blob); - Self { commitment, blob } + pub fn into_blob_buf(self) -> Vec { + self.blob_buf } } -/// Store the bitcoin block and references to the relevant transactions within the block +/// Indexed tx entry with some messages. +pub type RelevantTxEntry = IndexedTxEntry; + +/// Stores the bitcoin block and interpretations of relevant transactions within +/// the block. #[derive(Clone, Debug)] pub struct BlockData { + /// Block number. block_num: u64, + + /// Raw block data. + // TODO remove? block: Block, + /// Transactions in the block that contain protocol operations - protocol_txs: Vec, + relevant_txs: Vec, } impl BlockData { - pub fn new(block_num: u64, block: Block, protocol_txs: Vec) -> Self { + pub fn new(block_num: u64, block: Block, relevant_txs: Vec) -> Self { Self { block_num, block, - protocol_txs, + relevant_txs, } } - pub fn block(&self) -> &Block { - &self.block + pub fn block_num(&self) -> u64 { + self.block_num } - pub fn protocol_tx_idxs(&self) -> impl Iterator + '_ { - self.protocol_txs.iter().map(|v| v.index) + pub fn block(&self) -> &Block { + &self.block } - pub fn protocol_txs(&self) -> &[ProtocolTxEntry] { - &self.protocol_txs + pub fn relevant_txs(&self) -> &[RelevantTxEntry] { + &self.relevant_txs } - pub fn block_num(&self) -> u64 { - self.block_num + pub fn tx_idxs_iter(&self) -> impl Iterator + '_ { + self.relevant_txs.iter().map(|v| v.index) } } diff --git a/crates/proof-impl/btc-blockspace/src/filter.rs b/crates/proof-impl/btc-blockspace/src/filter.rs index ffc9e8b88..36fc694df 100644 --- a/crates/proof-impl/btc-blockspace/src/filter.rs +++ b/crates/proof-impl/btc-blockspace/src/filter.rs @@ -2,18 +2,16 @@ //! deposits, forced inclusion transactions as well as state updates use bitcoin::Block; -use strata_l1tx::filter::{ - indexer::{BlockIndexer, OpIndexer}, - TxFilterConfig, -}; +use strata_l1tx::filter::{indexer::index_block, TxFilterConfig}; use strata_primitives::{block_credential::CredRule, params::RollupParams}; use strata_state::{ batch::BatchCheckpoint, tx::{DepositInfo, ProtocolOperation}, }; -use crate::tx_indexer::ProverTxIndexer; +use crate::tx_indexer::ProverTxVisitorImpl; +// FIXME: needs better name pub fn extract_relevant_info( block: &Block, rollup_params: &RollupParams, @@ -22,10 +20,9 @@ pub fn extract_relevant_info( let mut deposits = Vec::new(); let mut prev_checkpoint = None; - let indexer = OpIndexer::new(ProverTxIndexer::new(), filter_config); - let (tx_refs, _, _) = indexer.index_block(block).finalize().into_parts(); + let tx_entries = index_block(block, ProverTxVisitorImpl::new, filter_config); - for op in tx_refs.into_iter().flat_map(|t| t.proto_ops().to_vec()) { + for op in tx_entries.into_iter().flat_map(|t| t.into_contents()) { match op { ProtocolOperation::Deposit(deposit_info) => { deposits.push(deposit_info.clone()); @@ -36,6 +33,7 @@ pub fn extract_relevant_info( } let batch: BatchCheckpoint = signed_batch.clone().into(); // Note: This assumes we will have one proper update + // FIXME: ^what if we have improper updates or more than one proper update? prev_checkpoint = prev_checkpoint.or(Some(batch)); } _ => {} diff --git a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs index 1fd1f4eab..7b5638c2e 100644 --- a/crates/proof-impl/btc-blockspace/src/tx_indexer.rs +++ b/crates/proof-impl/btc-blockspace/src/tx_indexer.rs @@ -1,44 +1,47 @@ -use digest::Digest; -use sha2::Sha256; -use strata_l1tx::{filter::indexer::TxIndexer, messages::L1TxExtract}; +use strata_l1tx::filter::indexer::TxVisitor; use strata_state::{ batch::SignedBatchCheckpoint, - tx::{DepositInfo, ProtocolOperation}, + tx::{DaCommitment, DepositInfo, ProtocolOperation}, }; -/// Ops indexer for Prover. Basically this efficiently gets da commitment from chunks without doing -/// anything else with the chunks. +/// Ops indexer for use with the prover. +/// +/// This just extracts *only* the protocol operations, in particular avoiding +/// copying the DA payload again, since memory copies are more expensive in +/// proofs. #[derive(Debug, Clone)] -pub(crate) struct ProverTxIndexer { +pub(crate) struct ProverTxVisitorImpl { ops: Vec, } -impl ProverTxIndexer { +impl ProverTxVisitorImpl { pub fn new() -> Self { Self { ops: Vec::new() } } } -impl TxIndexer for ProverTxIndexer { +impl TxVisitor for ProverTxVisitorImpl { + type Output = Vec; + fn visit_da<'a>(&mut self, chunks: impl Iterator) { - let mut hasher = Sha256::new(); - for chunk in chunks { - hasher.update(chunk); - } - let hash: [u8; 32] = hasher.finalize().into(); - self.ops.push(ProtocolOperation::DaCommitment(hash.into())); + let commitment = DaCommitment::from_chunk_iter(chunks); + self.ops.push(ProtocolOperation::DaCommitment(commitment)); } - fn visit_deposit(&mut self, d: DepositInfo) { - self.ops.push(ProtocolOperation::Deposit(d)); + fn visit_deposit(&mut self, di: DepositInfo) { + self.ops.push(ProtocolOperation::Deposit(di)); } - fn visit_checkpoint(&mut self, chkpt: SignedBatchCheckpoint) { - self.ops.push(ProtocolOperation::Checkpoint(chkpt)); + fn visit_checkpoint(&mut self, ckpt: SignedBatchCheckpoint) { + self.ops.push(ProtocolOperation::Checkpoint(ckpt)); } - fn finalize(self) -> L1TxExtract { - L1TxExtract::new(self.ops, Vec::new(), Vec::new()) + fn finalize(self) -> Option> { + if self.ops.is_empty() { + None + } else { + Some(self.ops) + } } } @@ -52,10 +55,7 @@ mod test { Amount, Block, BlockHash, CompactTarget, ScriptBuf, Transaction, TxMerkleNode, }; use strata_btcio::test_utils::create_checkpoint_envelope_tx; - use strata_l1tx::filter::{ - indexer::{BlockIndexer, OpIndexer}, - TxFilterConfig, - }; + use strata_l1tx::filter::{indexer::index_block, TxFilterConfig}; use strata_primitives::{ l1::{payload::L1Payload, BitcoinAmount}, params::Params, @@ -67,7 +67,7 @@ mod test { ArbitraryGenerator, }; - use super::ProverTxIndexer; + use super::ProverTxVisitorImpl; const TEST_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5"; @@ -108,12 +108,11 @@ mod test { let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ProverTxVisitorImpl::new, &filter_config); assert_eq!(tx_entries.len(), 1, "Should find one relevant transaction"); - for op in tx_entries[0].proto_ops() { + for op in tx_entries[0].contents() { if let ProtocolOperation::Deposit(deposit_info) = op { assert_eq!(deposit_info.address, ee_addr, "EE address should match"); assert_eq!( @@ -140,8 +139,7 @@ mod test { let block = create_test_block(vec![irrelevant_tx]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ProverTxVisitorImpl::new, &filter_config); assert!( tx_entries.is_empty(), @@ -175,14 +173,13 @@ mod test { let block = create_test_block(vec![tx1, tx2]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ProverTxVisitorImpl::new, &filter_config); assert_eq!(tx_entries.len(), 2, "Should find two relevant transactions"); for (i, info) in tx_entries .iter() - .flat_map(|op_txs| op_txs.proto_ops()) + .flat_map(|op_txs| op_txs.contents()) .enumerate() { if let ProtocolOperation::Deposit(deposit_info) = info { @@ -239,8 +236,7 @@ mod test { // Create a block with single tx that has multiple ops let block = create_test_block(vec![tx]); - let ops_indexer = OpIndexer::new(ProverTxIndexer::new(), &filter_config); - let (tx_entries, _, _) = ops_indexer.index_block(&block).finalize().into_parts(); + let tx_entries = index_block(&block, ProverTxVisitorImpl::new, &filter_config); assert_eq!( tx_entries.len(), @@ -248,20 +244,21 @@ mod test { "Should find one matching transaction entry" ); assert_eq!( - tx_entries[0].proto_ops().len(), + tx_entries[0].contents().len(), 2, "Should find two protocol ops" ); let mut dep_count = 0; let mut ckpt_count = 0; - for op in tx_entries[0].proto_ops() { + for op in tx_entries[0].contents() { match op { ProtocolOperation::Deposit(_) => dep_count += 1, ProtocolOperation::Checkpoint(_) => ckpt_count += 1, _ => {} } } + assert_eq!(dep_count, 1, "should have one deposit"); assert_eq!(ckpt_count, 1, "should have one checkpoint"); } diff --git a/crates/state/src/tx.rs b/crates/state/src/tx.rs index fddd1a137..3372fd9ab 100644 --- a/crates/state/src/tx.rs +++ b/crates/state/src/tx.rs @@ -1,6 +1,8 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; +use digest::Digest; use serde::{Deserialize, Serialize}; +use sha2::Sha256; use strata_primitives::{ buf::Buf32, l1::{BitcoinAmount, OutputRef}, @@ -8,7 +10,55 @@ use strata_primitives::{ use crate::batch::SignedBatchCheckpoint; -/// Information related to relevant transactions to be stored in an `L1Tx`. +/// Commits to a DA blob. This is just the hash of the DA blob. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + Arbitrary, + Serialize, + Deserialize, +)] +pub struct DaCommitment(Buf32); + +impl DaCommitment { + /// Creates a commitment from a DA payload buf. + pub fn from_buf(buf: &[u8]) -> Self { + Self::from_chunk_iter([buf].into_iter()) + } + + /// Creates a commitment from a series of contiguous chunks of a single DA + /// paylod buf. + /// + /// This is meant to be used when constructing a commitment from an in-situ + /// payload from a transaction, which has to be in 520-byte chunks. + pub fn from_chunk_iter<'a>(chunks: impl Iterator) -> Self { + // TODO maybe abstract this further? + let mut hasher = Sha256::new(); + for chunk in chunks { + hasher.update(chunk); + } + + let hash: [u8; 32] = hasher.finalize().into(); + Self(Buf32(hash)) + } + + pub fn as_hash(&self) -> &Buf32 { + &self.0 + } + + pub fn to_hash(&self) -> Buf32 { + self.0 + } +} + +/// Consensus level protocol operations extracted from a bitcoin transaction. +/// +/// These are submitted to the OL STF and impact state. #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, )] @@ -16,13 +66,17 @@ use crate::batch::SignedBatchCheckpoint; pub enum ProtocolOperation { /// Deposit Transaction Deposit(DepositInfo), - /// Deposit Request info - DepositRequest(DepositRequestInfo), + /// Checkpoint data Checkpoint(SignedBatchCheckpoint), - // Da Commitment - DaCommitment(Buf32), - // TODO: add other kinds like proofs + + /// DA blob + DaCommitment(DaCommitment), + + /// Deposit request. + /// + /// This is being removed soon as it's not really a consensus change. + DepositRequest(DepositRequestInfo), } #[derive( From aecb2680992d91e88edf5b624c9de22aedf02935 Mon Sep 17 00:00:00 2001 From: Trey Del Bonis Date: Wed, 5 Feb 2025 22:58:16 -0500 Subject: [PATCH 22/22] l1tx: cleanup --- bin/datatool/Cargo.toml | 2 +- bin/prover-client/Cargo.toml | 2 +- crates/l1tx/src/filter/indexer.rs | 106 ------------------------------ crates/l1tx/src/messages.rs | 9 +-- provers/sp1/Cargo.toml | 3 +- 5 files changed, 5 insertions(+), 117 deletions(-) diff --git a/bin/datatool/Cargo.toml b/bin/datatool/Cargo.toml index 29b612077..3eaffa521 100644 --- a/bin/datatool/Cargo.toml +++ b/bin/datatool/Cargo.toml @@ -29,5 +29,5 @@ zeroize.workspace = true default = [] risc0-builder = ["strata-risc0-guest-builder", "bytemuck"] sp1-builder = ["strata-sp1-guest-builder/sp1-dev"] -sp1-mock-builder = ["sp1-builder", "strata-sp1-guest-builder/mock"] sp1-docker-builder = ["sp1-builder", "strata-sp1-guest-builder/docker-build"] +sp1-mock-builder = ["sp1-builder", "strata-sp1-guest-builder/mock"] diff --git a/bin/prover-client/Cargo.toml b/bin/prover-client/Cargo.toml index f977bc9a7..c5412850f 100644 --- a/bin/prover-client/Cargo.toml +++ b/bin/prover-client/Cargo.toml @@ -64,8 +64,8 @@ strata-test-utils.workspace = true [features] default = [] sp1 = ["zkaleido-sp1-adapter/prover"] -sp1-mock = ["sp1", "zkaleido-sp1-adapter/mock"] sp1-builder = ["sp1", "strata-sp1-guest-builder/prover"] +sp1-mock = ["sp1", "zkaleido-sp1-adapter/mock"] sp1-mock-builder = [ "sp1-builder", "zkaleido-sp1-adapter/mock", diff --git a/crates/l1tx/src/filter/indexer.rs b/crates/l1tx/src/filter/indexer.rs index 107b053fb..f7b762e5a 100644 --- a/crates/l1tx/src/filter/indexer.rs +++ b/crates/l1tx/src/filter/indexer.rs @@ -73,112 +73,6 @@ fn index_tx( visitor.finalize() } -/* -/// Interface to extract relevant information from a block. -pub trait BlockIndexer { - /// Output from the indexing pass. - type Output; - - /// Indexes the block and produces the output. - fn index_block(&self, block: &Block) -> Self::Output; -} - -/// Indexes `ProtocolTxEntry`s, `DepositRequestInfo`s and `DaEntry`s from a bitcoin block. -/// -/// Currently, this is used from two contexts: rollup node and prover node, each of which will have -/// different `TxIndexer`s which determine what and how something is extracted from a transaction. -pub struct TxOpIndexer<'a, T: TxVisitor, F: Fn() -> T> { - /// The actual logic of what and how something is extracted from a transaction. - tx_indexer_fn: F, - - /// The config that's used to filter transactions and extract data. This has a lifetime - /// parameter for two reasons: 1) It is used in prover context so using Arc might incur some - /// overheads, 2) We can be sure that the config won't change during indexing of a l1 block. - filter_config: &'a TxFilterConfig, - - /// `ProtocolTxEntry`s will be accumulated here. - relevant_txs: Vec>, -} - -impl<'a, T: TxVisitor, F: Fn() -> T> TxOpIndexer<'a, T, F> { - pub fn new(tx_indexer_fn: F, filter_config: &'a TxFilterConfig) -> Self { - Self { - tx_indexer_fn, - filter_config, - relevant_txs: Vec::new(), - dep_reqs: Vec::new(), - da_entries: Vec::new(), - } - } - - pub fn tx_entries(&self) -> &[IndexedTxEntry] { - &self.relevant_txs - } - - pub fn deposit_requests(&self) -> &[DepositRequestInfo] { - &self.dep_reqs - } - - pub fn da_entries(&self) -> &[DaEntry] { - &self.da_entries - } -} - -impl T> TxOpIndexer<'_, T, F> { - fn index_tx(&self, txidx: u32, tx: &Transaction) { - let mut tx_visitor = (self.tx_indexer_fn)(); - - for ckpt in parse_checkpoint_envelopes(tx, self.filter_config) { - tx_visitor.visit_checkpoint(ckpt); - } - - for dp in parse_deposits(tx, self.filter_config) { - tx_visitor.visit_deposit(dp); - } - - // TODO: remove this later when we do not require deposit request ops - for dr in parse_deposit_requests(tx, self.filter_config) { - tx_visitor.visit_deposit_request(dr); - } - - for da in parse_da_blobs(tx, self.filter_config) { - tx_visitor.visit_da(da); - } - - // Finalize the visitor. If there's nothing to report then return - // immeditately. - if let Some(summary) = tx_visitor.finalize() { - self.relevant_txs.push(IndexedTxEntry::new(txidx, summary)); - } - - self.dep_reqs.append(&mut deps); - self.da_entries.append(&mut das); - } -} - -impl T> BlockIndexer for TxOpIndexer<'_, T, F> { - type Output = Vec; - - fn index_block(&self, block: &Block) -> Self::Output - where - Self: Sized, - { - let mut op_txs = Vec::new(); - let mut deposit_reqs = Vec::new(); - let mut da_entries = Vec::new(); - - for (i, tx) in block.txdata.iter().enumerate() { - self.index_tx(i as u32, tx); - } - - // TODO - } - - /*fn finalize(self) -> L1BlockExtract { - L1BlockExtract::new(self.tx_entries, self.dep_reqs, self.da_entries) - }*/ -}*/ - /// Generic no-op tx indexer that emits nothing for every tx but could /// substitute for any type of visitor. pub struct NopTxVisitorImpl(::std::marker::PhantomData); diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 13ec95103..668fe854d 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -21,7 +21,7 @@ pub enum L1Event { GenesisVerificationState(u64, HeaderVerificationState), } -/// Indexed transaction entry taken from +/// Indexed transaction entry taken from a block. #[derive(Clone, Debug, BorshDeserialize, BorshSerialize)] pub struct IndexedTxEntry { /// Index of the transaction in the block @@ -55,11 +55,6 @@ impl IndexedTxEntry { } } -/* - * Core protocol specific bitcoin transaction reference. A bitcoin transaction can have multiple - * operations relevant to protocol. This is used in the context of [`BlockData`]. - */ - /// Container for the different kinds of messages that we could extract from a L1 tx. #[derive(Clone, Debug)] pub struct L1TxMessages { @@ -110,7 +105,7 @@ impl L1TxMessages { } } -/// Da data retrieved from L1 transaction. +/// DA commitment and blob retrieved from L1 transaction. #[derive(Clone, Debug)] pub struct DaEntry { #[allow(unused)] diff --git a/provers/sp1/Cargo.toml b/provers/sp1/Cargo.toml index b9aded094..a430e0573 100644 --- a/provers/sp1/Cargo.toml +++ b/provers/sp1/Cargo.toml @@ -12,8 +12,8 @@ zkaleido-sp1-adapter = { git = "https://github.com/alpenlabs/zkaleido", tag = "v [build-dependencies] bincode.workspace = true cargo_metadata = "0.19.1" -sha2.workspace = true cfg-if.workspace = true +sha2.workspace = true sp1-helper = { git = "https://github.com/succinctlabs/sp1.git", rev = "6c5a7f2846cd3610ecd38b1641f0e370fd07ee83" } sp1-sdk = "4.0.0" @@ -22,5 +22,4 @@ default = ["prover"] docker-build = [] mock = [] prover = ["zkaleido-sp1-adapter"] -docker-build = [] sp1-dev = []