diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index 06cbb71358..2cc86259bd 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -146,6 +146,7 @@ mod tests { use katana_primitives::genesis::Genesis; use katana_primitives::receipt::{InvokeTxReceipt, Receipt}; use katana_primitives::state::StateUpdatesWithDeclaredClasses; + use katana_primitives::trace::TxExecInfo; use katana_primitives::transaction::{InvokeTx, Tx, TxWithHash}; use katana_primitives::FieldElement; use katana_provider::providers::in_memory::InMemoryProvider; @@ -154,7 +155,7 @@ mod tests { HeaderProvider, }; use katana_provider::traits::state::StateFactoryProvider; - use katana_provider::traits::transaction::TransactionProvider; + use katana_provider::traits::transaction::{TransactionProvider, TransactionTraceProvider}; use starknet::macros::felt; use super::Blockchain; @@ -254,7 +255,7 @@ mod tests { dummy_block.clone(), StateUpdatesWithDeclaredClasses::default(), vec![Receipt::Invoke(InvokeTxReceipt::default())], - vec![], + vec![TxExecInfo::default()], ) .unwrap(); @@ -310,11 +311,14 @@ mod tests { .unwrap(); let tx = blockchain.provider().transaction_by_hash(dummy_tx.hash).unwrap().unwrap(); + let tx_exec = + blockchain.provider().transaction_execution(dummy_tx.hash).unwrap().unwrap(); assert_eq!(block_hash, dummy_block.block.header.hash); assert_eq!(block_number, dummy_block.block.header.header.number); assert_eq!(block, dummy_block.block.unseal()); assert_eq!(tx, dummy_tx); + assert_eq!(tx_exec, TxExecInfo::default()); } } } diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index 68129d43e0..47a6436791 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -44,7 +44,7 @@ pub enum TableType { DupSort, } -pub const NUM_TABLES: usize = 22; +pub const NUM_TABLES: usize = 23; /// Macro to declare `libmdbx` tables. #[macro_export] @@ -153,6 +153,7 @@ define_tables_enum! {[ (TxNumbers, TableType::Table), (TxBlocks, TableType::Table), (TxHashes, TableType::Table), + (TxTraces, TableType::Table), (Transactions, TableType::Table), (Receipts, TableType::Table), (CompiledClassHashes, TableType::Table), @@ -189,8 +190,8 @@ tables! { Transactions: (TxNumber) => Tx, /// Stores the block number of a transaction. TxBlocks: (TxNumber) => BlockNumber, - /// Stores the transaction's execution info. - TxExecutions: (TxNumber) => TxExecInfo, + /// Stores the transaction's traces. + TxTraces: (TxNumber) => TxExecInfo, /// Store transaction receipts Receipts: (TxNumber) => Receipt, /// Store compiled classes @@ -242,20 +243,21 @@ mod tests { assert_eq!(Tables::ALL[5].name(), TxNumbers::NAME); assert_eq!(Tables::ALL[6].name(), TxBlocks::NAME); assert_eq!(Tables::ALL[7].name(), TxHashes::NAME); - assert_eq!(Tables::ALL[8].name(), Transactions::NAME); - assert_eq!(Tables::ALL[9].name(), Receipts::NAME); - assert_eq!(Tables::ALL[10].name(), CompiledClassHashes::NAME); - assert_eq!(Tables::ALL[11].name(), CompiledClasses::NAME); - assert_eq!(Tables::ALL[12].name(), SierraClasses::NAME); - assert_eq!(Tables::ALL[13].name(), ContractInfo::NAME); - assert_eq!(Tables::ALL[14].name(), ContractStorage::NAME); - assert_eq!(Tables::ALL[15].name(), ClassDeclarationBlock::NAME); - assert_eq!(Tables::ALL[16].name(), ClassDeclarations::NAME); - assert_eq!(Tables::ALL[17].name(), ContractInfoChangeSet::NAME); - assert_eq!(Tables::ALL[18].name(), NonceChangeHistory::NAME); - assert_eq!(Tables::ALL[19].name(), ClassChangeHistory::NAME); - assert_eq!(Tables::ALL[20].name(), StorageChangeHistory::NAME); - assert_eq!(Tables::ALL[21].name(), StorageChangeSet::NAME); + assert_eq!(Tables::ALL[8].name(), TxTraces::NAME); + assert_eq!(Tables::ALL[9].name(), Transactions::NAME); + assert_eq!(Tables::ALL[10].name(), Receipts::NAME); + assert_eq!(Tables::ALL[11].name(), CompiledClassHashes::NAME); + assert_eq!(Tables::ALL[12].name(), CompiledClasses::NAME); + assert_eq!(Tables::ALL[13].name(), SierraClasses::NAME); + assert_eq!(Tables::ALL[14].name(), ContractInfo::NAME); + assert_eq!(Tables::ALL[15].name(), ContractStorage::NAME); + assert_eq!(Tables::ALL[16].name(), ClassDeclarationBlock::NAME); + assert_eq!(Tables::ALL[17].name(), ClassDeclarations::NAME); + assert_eq!(Tables::ALL[18].name(), ContractInfoChangeSet::NAME); + assert_eq!(Tables::ALL[19].name(), NonceChangeHistory::NAME); + assert_eq!(Tables::ALL[20].name(), ClassChangeHistory::NAME); + assert_eq!(Tables::ALL[21].name(), StorageChangeHistory::NAME); + assert_eq!(Tables::ALL[22].name(), StorageChangeSet::NAME); assert_eq!(Tables::Headers.table_type(), TableType::Table); assert_eq!(Tables::BlockHashes.table_type(), TableType::Table); @@ -265,6 +267,7 @@ mod tests { assert_eq!(Tables::TxNumbers.table_type(), TableType::Table); assert_eq!(Tables::TxBlocks.table_type(), TableType::Table); assert_eq!(Tables::TxHashes.table_type(), TableType::Table); + assert_eq!(Tables::TxTraces.table_type(), TableType::Table); assert_eq!(Tables::Transactions.table_type(), TableType::Table); assert_eq!(Tables::Receipts.table_type(), TableType::Table); assert_eq!(Tables::CompiledClassHashes.table_type(), TableType::Table); diff --git a/crates/katana/storage/provider/src/error.rs b/crates/katana/storage/provider/src/error.rs index 6fe75d4c6d..531c37dfc9 100644 --- a/crates/katana/storage/provider/src/error.rs +++ b/crates/katana/storage/provider/src/error.rs @@ -55,6 +55,10 @@ pub enum ProviderError { #[error("Missing transaction receipt for tx number {0}")] MissingTxReceipt(TxNumber), + /// Error when a transaction execution info is not found but the transaction exists. + #[error("Missing transaction execution for tx number {0}")] + MissingTxExecution(TxNumber), + /// Error when a compiled class hash is not found but the class hash exists. #[error("Missing compiled class hash for class hash {0:#x}")] MissingCompiledClassHash(ClassHash), diff --git a/crates/katana/storage/provider/src/providers/db/mod.rs b/crates/katana/storage/provider/src/providers/db/mod.rs index e4687ada22..5421ba7eb7 100644 --- a/crates/katana/storage/provider/src/providers/db/mod.rs +++ b/crates/katana/storage/provider/src/providers/db/mod.rs @@ -492,15 +492,40 @@ impl TransactionStatusProvider for DbProvider { } impl TransactionTraceProvider for DbProvider { - fn transaction_execution(&self, _hash: TxHash) -> ProviderResult> { - todo!() + fn transaction_execution(&self, hash: TxHash) -> ProviderResult> { + let db_tx = self.0.tx()?; + if let Some(num) = db_tx.get::(hash)? { + let execution = db_tx + .get::(num)? + .ok_or(ProviderError::MissingTxExecution(num))?; + + db_tx.commit()?; + Ok(Some(execution)) + } else { + Ok(None) + } } fn transactions_executions_by_block( &self, - _block_id: BlockHashOrNumber, + block_id: BlockHashOrNumber, ) -> ProviderResult>> { - todo!() + if let Some(indices) = self.block_body_indices(block_id)? { + let db_tx = self.0.tx()?; + let mut executions = Vec::with_capacity(indices.tx_count as usize); + + let range = Range::from(indices); + for i in range { + if let Some(execution) = db_tx.get::(i)? { + executions.push(execution); + } + } + + db_tx.commit()?; + Ok(Some(executions)) + } else { + Ok(None) + } } } @@ -508,9 +533,8 @@ impl ReceiptProvider for DbProvider { fn receipt_by_hash(&self, hash: TxHash) -> ProviderResult> { let db_tx = self.0.tx()?; if let Some(num) = db_tx.get::(hash)? { - let receipt = db_tx - .get::(num)? - .ok_or(ProviderError::MissingTxReceipt(num))?; + let receipt = + db_tx.get::(num)?.ok_or(ProviderError::MissingTxReceipt(num))?; db_tx.commit()?; Ok(Some(receipt)) @@ -561,7 +585,7 @@ impl BlockWriter for DbProvider { block: SealedBlockWithStatus, states: StateUpdatesWithDeclaredClasses, receipts: Vec, - _executions: Vec, + executions: Vec, ) -> ProviderResult<()> { self.0.update(move |db_tx| -> ProviderResult<()> { let block_hash = block.block.header.hash; @@ -581,7 +605,13 @@ impl BlockWriter for DbProvider { db_tx.put::(block_number, block_header)?; db_tx.put::(block_number, block_body_indices)?; - for (i, (transaction, receipt)) in transactions.into_iter().zip(receipts).enumerate() { + for (i, (transaction, receipt, execution)) in transactions + .into_iter() + .zip(receipts.into_iter()) + .zip(executions.into_iter()) + .map(|((transaction, receipt), execution)| (transaction, receipt, execution)) + .enumerate() + { let tx_number = tx_offset + i as u64; let tx_hash = transaction.hash; @@ -590,6 +620,7 @@ impl BlockWriter for DbProvider { db_tx.put::(tx_number, block_number)?; db_tx.put::(tx_number, transaction.transaction)?; db_tx.put::(tx_number, receipt)?; + db_tx.put::(tx_number, execution)?; } // insert classes @@ -727,6 +758,7 @@ mod tests { use katana_primitives::contract::ContractAddress; use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; + use katana_primitives::trace::TxExecInfo; use katana_primitives::transaction::{InvokeTx, Tx, TxHash, TxWithHash}; use starknet::macros::felt; @@ -812,7 +844,7 @@ mod tests { block.clone(), state_updates, vec![Receipt::Invoke(Default::default())], - vec![], + vec![TxExecInfo::default()], ) .expect("failed to insert block"); @@ -890,7 +922,7 @@ mod tests { block.clone(), state_updates1, vec![Receipt::Invoke(Default::default())], - vec![], + vec![TxExecInfo::default()], ) .expect("failed to insert block"); @@ -900,7 +932,7 @@ mod tests { block, state_updates2, vec![Receipt::Invoke(Default::default())], - vec![], + vec![TxExecInfo::default()], ) .expect("failed to insert block"); diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 98a2bd991e..16237cb7d3 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -456,7 +456,7 @@ impl BlockWriter for ForkedProvider { block: SealedBlockWithStatus, states: StateUpdatesWithDeclaredClasses, receipts: Vec, - _executions: Vec, + executions: Vec, ) -> ProviderResult<()> { let mut storage = self.storage.write(); @@ -494,6 +494,7 @@ impl BlockWriter for ForkedProvider { storage.transaction_numbers.extend(txs_num); storage.transaction_block.extend(txs_block); storage.receipts.extend(receipts); + storage.transactions_executions.extend(executions); storage.state_update.insert(block_number, states.state_updates.clone()); diff --git a/crates/katana/storage/provider/tests/block.rs b/crates/katana/storage/provider/tests/block.rs index 83861e52a4..c2ff3b3a8b 100644 --- a/crates/katana/storage/provider/tests/block.rs +++ b/crates/katana/storage/provider/tests/block.rs @@ -15,7 +15,7 @@ use katana_provider::traits::env::BlockEnvProvider; use katana_provider::traits::state::StateRootProvider; use katana_provider::traits::state_update::StateUpdateProvider; use katana_provider::traits::transaction::{ - ReceiptProvider, TransactionProvider, TransactionStatusProvider, + ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionTraceProvider, }; use katana_provider::BlockchainProvider; use rstest_reuse::{self, *}; @@ -27,7 +27,7 @@ use fixtures::{ db_provider, fork_provider, fork_provider_with_spawned_fork_network, in_memory_provider, mock_state_updates, provider_with_states, }; -use utils::generate_dummy_blocks_and_receipts; +use starknet::core::types::FieldElement; #[apply(insert_block_cases)] fn insert_block_with_in_memory_provider( @@ -53,6 +53,30 @@ fn insert_block_with_db_provider( insert_block_test_impl(provider, block_count) } +#[apply(insert_block_cases)] +fn insert_block_empty_with_in_memory_provider( + #[from(in_memory_provider)] provider: BlockchainProvider, + #[case] block_count: u64, +) -> Result<()> { + insert_block_empty_test_impl(provider, block_count) +} + +#[apply(insert_block_cases)] +fn insert_block_empty_with_fork_provider( + #[from(fork_provider)] provider: BlockchainProvider, + #[case] block_count: u64, +) -> Result<()> { + insert_block_empty_test_impl(provider, block_count) +} + +#[apply(insert_block_cases)] +fn insert_block_empty_with_db_provider( + #[from(db_provider)] provider: BlockchainProvider, + #[case] block_count: u64, +) -> Result<()> { + insert_block_empty_test_impl(provider, block_count) +} + fn insert_block_test_impl(provider: BlockchainProvider, count: u64) -> Result<()> where Db: BlockProvider @@ -60,19 +84,20 @@ where + ReceiptProvider + StateRootProvider + TransactionStatusProvider + + TransactionTraceProvider + BlockEnvProvider, { - let blocks = generate_dummy_blocks_and_receipts(count); + let blocks = utils::generate_dummy_blocks_and_receipts(count); let txs: Vec = - blocks.iter().flat_map(|(block, _)| block.block.body.clone()).collect(); + blocks.iter().flat_map(|(block, _, _)| block.block.body.clone()).collect(); let total_txs = txs.len() as u64; - for (block, receipts) in &blocks { + for (block, receipts, executions) in &blocks { provider.insert_block_with_states_and_receipts( block.clone(), Default::default(), receipts.clone(), - Default::default(), + executions.clone(), )?; assert_eq!(provider.latest_number().unwrap(), block.block.header.header.number); @@ -91,7 +116,7 @@ where blocks.clone().into_iter().map(|b| b.0.block.unseal()).collect::>() ); - for (block, receipts) in blocks { + for (block, receipts, executions) in blocks { let block_id = BlockHashOrNumber::Hash(block.block.header.hash); let expected_block_num = block.block.header.header.number; @@ -114,6 +139,7 @@ where let actual_block_tx_count = provider.transaction_count_by_block(block_id)?; let actual_receipts = provider.receipts_by_block(block_id)?; + let actual_executions = provider.transactions_executions_by_block(block_id)?; let expected_block_with_tx_hashes = BlockWithTxHashes { header: expected_block.header.clone(), @@ -128,6 +154,7 @@ where for (idx, tx) in expected_block.body.iter().enumerate() { let actual_receipt = provider.receipt_by_hash(tx.hash)?; + let actual_execution = provider.transaction_execution(tx.hash)?; let actual_tx = provider.transaction_by_hash(tx.hash)?; let actual_tx_status = provider.transaction_status(tx.hash)?; let actual_tx_block_num_hash = provider.transaction_block_num_and_hash(tx.hash)?; @@ -137,6 +164,7 @@ where assert_eq!(actual_tx_block_num_hash, Some((expected_block_num, expected_block_hash))); assert_eq!(actual_tx_status, Some(FinalityStatus::AcceptedOnL2)); assert_eq!(actual_receipt, Some(receipts[idx].clone())); + assert_eq!(actual_execution, Some(executions[idx].clone())); assert_eq!(actual_tx_by_block_idx, Some(tx.clone())); assert_eq!(actual_tx, Some(tx.clone())); } @@ -145,6 +173,109 @@ where assert_eq!(actual_receipts.as_ref().map(|r| r.len()), Some(expected_block.body.len())); assert_eq!(actual_receipts, Some(receipts)); + assert_eq!(actual_executions, Some(executions)); + + assert_eq!(actual_block_tx_count, Some(expected_block.body.len() as u64)); + assert_eq!(actual_state_root, Some(expected_block.header.state_root)); + assert_eq!(actual_block_txs, Some(expected_block.body.clone())); + assert_eq!(actual_block_hash, Some(expected_block_hash)); + assert_eq!(actual_block, Some(expected_block)); + } + + Ok(()) +} + +fn insert_block_empty_test_impl(provider: BlockchainProvider, count: u64) -> Result<()> +where + Db: BlockProvider + + BlockWriter + + ReceiptProvider + + StateRootProvider + + TransactionStatusProvider + + TransactionTraceProvider + + BlockEnvProvider, +{ + let blocks = utils::generate_dummy_blocks_empty(count); + let txs: Vec = blocks.iter().flat_map(|block| block.block.body.clone()).collect(); + + let total_txs = txs.len() as u64; + assert_eq!(total_txs, 0); + + for block in &blocks { + provider.insert_block_with_states_and_receipts( + block.clone(), + Default::default(), + vec![], + vec![], + )?; + + assert_eq!(provider.latest_number().unwrap(), block.block.header.header.number); + assert_eq!(provider.latest_hash().unwrap(), block.block.header.hash); + } + + let actual_blocks_in_range = provider.blocks_in_range(0..=count)?; + + assert_eq!(actual_blocks_in_range.len(), count as usize); + assert_eq!( + actual_blocks_in_range, + blocks.clone().into_iter().map(|b| b.block.unseal()).collect::>() + ); + + for block in blocks { + let block_id = BlockHashOrNumber::Hash(block.block.header.hash); + + let expected_block_num = block.block.header.header.number; + let expected_block_hash = block.block.header.hash; + let expected_block = block.block.unseal(); + + let expected_block_env = BlockEnv { + number: expected_block_num, + timestamp: expected_block.header.timestamp, + l1_gas_prices: expected_block.header.gas_prices.clone(), + sequencer_address: expected_block.header.sequencer_address, + }; + + let actual_block_hash = provider.block_hash_by_num(expected_block_num)?; + + let actual_block = provider.block(block_id)?; + let actual_block_txs = provider.transactions_by_block(block_id)?; + let actual_status = provider.block_status(block_id)?; + let actual_state_root = provider.state_root(block_id)?; + + let actual_block_tx_count = provider.transaction_count_by_block(block_id)?; + let actual_receipts = provider.receipts_by_block(block_id)?; + let actual_executions = provider.transactions_executions_by_block(block_id)?; + + let expected_block_with_tx_hashes = + BlockWithTxHashes { header: expected_block.header.clone(), body: vec![] }; + + let actual_block_with_tx_hashes = provider.block_with_tx_hashes(block_id)?; + let actual_block_env = provider.block_env_at(block_id)?; + + assert_eq!(actual_status, Some(FinalityStatus::AcceptedOnL2)); + assert_eq!(actual_block_with_tx_hashes, Some(expected_block_with_tx_hashes)); + + let tx_hash = FieldElement::ZERO; + + let actual_receipt = provider.receipt_by_hash(tx_hash)?; + let actual_execution = provider.transaction_execution(tx_hash)?; + let actual_tx = provider.transaction_by_hash(tx_hash)?; + let actual_tx_status = provider.transaction_status(tx_hash)?; + let actual_tx_block_num_hash = provider.transaction_block_num_and_hash(tx_hash)?; + let actual_tx_by_block_idx = provider.transaction_by_block_and_idx(block_id, 0)?; + + assert_eq!(actual_tx_block_num_hash, None); + assert_eq!(actual_tx_status, None); + assert_eq!(actual_receipt, None); + assert_eq!(actual_execution, None); + assert_eq!(actual_tx_by_block_idx, None); + assert_eq!(actual_tx, None); + + assert_eq!(actual_block_env, Some(expected_block_env)); + + assert_eq!(actual_receipts.as_ref().map(|r| r.len()), Some(expected_block.body.len())); + assert_eq!(actual_receipts, Some(vec![])); + assert_eq!(actual_executions, Some(vec![])); assert_eq!(actual_block_tx_count, Some(expected_block.body.len() as u64)); assert_eq!(actual_state_root, Some(expected_block.header.state_root)); diff --git a/crates/katana/storage/provider/tests/utils.rs b/crates/katana/storage/provider/tests/utils.rs index aaff5161bf..17795922c0 100644 --- a/crates/katana/storage/provider/tests/utils.rs +++ b/crates/katana/storage/provider/tests/utils.rs @@ -1,11 +1,15 @@ use katana_primitives::block::{Block, BlockHash, FinalityStatus, Header, SealedBlockWithStatus}; use katana_primitives::receipt::{InvokeTxReceipt, Receipt}; +use katana_primitives::trace::TxExecInfo; use katana_primitives::transaction::{InvokeTx, Tx, TxHash, TxWithHash}; use katana_primitives::FieldElement; -pub fn generate_dummy_txs_and_receipts(count: usize) -> (Vec, Vec) { +pub fn generate_dummy_txs_and_receipts( + count: usize, +) -> (Vec, Vec, Vec) { let mut txs = Vec::with_capacity(count); let mut receipts = Vec::with_capacity(count); + let mut executions = Vec::with_capacity(count); // TODO: generate random txs and receipts variants for _ in 0..count { @@ -15,20 +19,21 @@ pub fn generate_dummy_txs_and_receipts(count: usize) -> (Vec, Vec Vec<(SealedBlockWithStatus, Vec)> { +) -> Vec<(SealedBlockWithStatus, Vec, Vec)> { let mut blocks = Vec::with_capacity(count as usize); let mut parent_hash: BlockHash = 0u8.into(); for i in 0..count { let tx_count = (rand::random::() % 10) as usize; - let (body, receipts) = generate_dummy_txs_and_receipts(tx_count); + let (body, receipts, executions) = generate_dummy_txs_and_receipts(tx_count); let header = Header { parent_hash, number: i, ..Default::default() }; let block = @@ -39,8 +44,28 @@ pub fn generate_dummy_blocks_and_receipts( blocks.push(( SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }, receipts, + executions, )); } blocks } + +pub fn generate_dummy_blocks_empty(count: u64) -> Vec { + let mut blocks = Vec::with_capacity(count as usize); + let mut parent_hash: BlockHash = 0u8.into(); + + for i in 0..count { + let header = Header { parent_hash, number: i, ..Default::default() }; + let body = vec![]; + + let block = + Block { header, body }.seal_with_hash(FieldElement::from(rand::random::())); + + parent_hash = block.header.hash; + + blocks.push(SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }); + } + + blocks +}