diff --git a/Cargo.lock b/Cargo.lock index ef7ddfb644..358e302b51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7736,6 +7736,7 @@ name = "katana-rpc" version = "1.0.0-alpha.3" dependencies = [ "alloy", + "alloy-primitives", "anyhow", "assert_matches", "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.3.2)", diff --git a/crates/dojo-test-utils/src/sequencer.rs b/crates/dojo-test-utils/src/sequencer.rs index ea748aac3c..229eaf3934 100644 --- a/crates/dojo-test-utils/src/sequencer.rs +++ b/crates/dojo-test-utils/src/sequencer.rs @@ -83,7 +83,7 @@ impl TestSequencer { let url = Url::parse(&format!("http://{}", handle.addr)).expect("Failed to parse URL"); - let account = sequencer.backend.config.genesis.accounts().next().unwrap(); + let account = sequencer.backend().config.genesis.accounts().next().unwrap(); let account = TestAccount { private_key: Felt::from_bytes_be(&account.1.private_key().unwrap().to_bytes_be()), account_address: Felt::from_bytes_be(&account.0.to_bytes_be()), @@ -114,7 +114,7 @@ impl TestSequencer { &self, index: usize, ) -> SingleOwnerAccount, LocalWallet> { - let accounts: Vec<_> = self.sequencer.backend.config.genesis.accounts().collect::<_>(); + let accounts: Vec<_> = self.sequencer.backend().config.genesis.accounts().collect::<_>(); let account = accounts[index]; let private_key = Felt::from_bytes_be(&account.1.private_key().unwrap().to_bytes_be()); diff --git a/crates/katana/core/src/lib.rs b/crates/katana/core/src/lib.rs index bbeede3914..1c4482d022 100644 --- a/crates/katana/core/src/lib.rs +++ b/crates/katana/core/src/lib.rs @@ -7,5 +7,3 @@ pub mod pool; pub mod sequencer; pub mod service; pub mod utils; - -pub mod sequencer_error; diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index 5c71f865c3..970195ace4 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -1,35 +1,12 @@ -use std::cmp::Ordering; -use std::iter::Skip; -use std::slice::Iter; use std::sync::Arc; -use anyhow::Result; use katana_executor::ExecutorFactory; -use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockIdOrTag, BlockNumber}; -use katana_primitives::chain::ChainId; -use katana_primitives::class::{ClassHash, CompiledClass}; -use katana_primitives::contract::{ContractAddress, Nonce, StorageKey, StorageValue}; -use katana_primitives::env::BlockEnv; -use katana_primitives::event::{ContinuationToken, ContinuationTokenError}; -use katana_primitives::receipt::Event; -use katana_primitives::transaction::{ExecutableTxWithHash, TxHash, TxWithHash}; -use katana_primitives::FieldElement; -use katana_provider::traits::block::{ - BlockHashProvider, BlockIdReader, BlockNumberProvider, BlockProvider, -}; -use katana_provider::traits::contract::ContractClassProvider; -use katana_provider::traits::env::BlockEnvProvider; -use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; -use katana_provider::traits::transaction::{ - ReceiptProvider, TransactionProvider, TransactionsProviderExt, -}; -use starknet::core::types::{BlockTag, EmittedEvent, EventsPage}; +use katana_provider::BlockchainProvider; use crate::backend::config::StarknetConfig; -use crate::backend::contract::StarknetContract; +use crate::backend::storage::Database; use crate::backend::Backend; use crate::pool::TransactionPool; -use crate::sequencer_error::SequencerError; use crate::service::block_producer::{BlockProducer, BlockProducerMode, PendingExecutor}; #[cfg(feature = "messaging")] use crate::service::messaging::MessagingConfig; @@ -37,8 +14,6 @@ use crate::service::messaging::MessagingConfig; use crate::service::messaging::MessagingService; use crate::service::{NodeService, TransactionMiner}; -type SequencerResult = Result; - #[derive(Debug, Default)] pub struct SequencerConfig { pub block_time: Option, @@ -49,10 +24,10 @@ pub struct SequencerConfig { #[allow(missing_debug_implementations)] pub struct KatanaSequencer { - pub config: SequencerConfig, - pub pool: Arc, - pub backend: Arc>, - pub block_producer: Arc>, + config: SequencerConfig, + pool: Arc, + backend: Arc>, + block_producer: Arc>, } impl KatanaSequencer { @@ -113,327 +88,16 @@ impl KatanaSequencer { &self.backend } - pub fn block_env_at(&self, block_id: BlockIdOrTag) -> SequencerResult> { - let provider = self.backend.blockchain.provider(); - - if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { - if let Some(exec) = self.pending_executor() { - return Ok(Some(exec.read().block_env())); - } - } - - match block_id { - BlockIdOrTag::Tag(BlockTag::Pending) | BlockIdOrTag::Tag(BlockTag::Latest) => { - let num = provider.latest_number()?; - provider - .block_env_at(num.into())? - .map(Some) - .ok_or(SequencerError::BlockNotFound(block_id)) - } - - BlockIdOrTag::Hash(hash) => provider - .block_env_at(hash.into())? - .map(Some) - .ok_or(SequencerError::BlockNotFound(block_id)), - - BlockIdOrTag::Number(num) => provider - .block_env_at(num.into())? - .map(Some) - .ok_or(SequencerError::BlockNotFound(block_id)), - } - } - - pub fn state(&self, block_id: &BlockIdOrTag) -> SequencerResult> { - let provider = self.backend.blockchain.provider(); - - match block_id { - BlockIdOrTag::Tag(BlockTag::Latest) => { - let state = StateFactoryProvider::latest(provider)?; - Ok(state) - } - - BlockIdOrTag::Tag(BlockTag::Pending) => { - if let Some(exec) = self.pending_executor() { - Ok(Box::new(exec.read().state())) - } else { - let state = StateFactoryProvider::latest(provider)?; - Ok(state) - } - } - - BlockIdOrTag::Hash(hash) => { - StateFactoryProvider::historical(provider, BlockHashOrNumber::Hash(*hash))? - .ok_or(SequencerError::BlockNotFound(*block_id)) - } - - BlockIdOrTag::Number(num) => { - StateFactoryProvider::historical(provider, BlockHashOrNumber::Num(*num))? - .ok_or(SequencerError::BlockNotFound(*block_id)) - } - } - } - - pub fn add_transaction_to_pool(&self, tx: ExecutableTxWithHash) { - self.pool.add_transaction(tx); - } - - pub fn block_hash_and_number(&self) -> SequencerResult<(BlockHash, BlockNumber)> { - let provider = self.backend.blockchain.provider(); - let hash = BlockHashProvider::latest_hash(provider)?; - let number = BlockNumberProvider::latest_number(provider)?; - Ok((hash, number)) - } - - pub fn class_hash_at( - &self, - block_id: BlockIdOrTag, - contract_address: ContractAddress, - ) -> SequencerResult> { - let state = self.state(&block_id)?; - let class_hash = StateProvider::class_hash_of_contract(&state, contract_address)?; - Ok(class_hash) - } - - pub fn class( - &self, - block_id: BlockIdOrTag, - class_hash: ClassHash, - ) -> SequencerResult> { - let state = self.state(&block_id)?; - - let Some(class) = ContractClassProvider::class(&state, class_hash)? else { - return Ok(None); - }; - - match class { - CompiledClass::Deprecated(class) => Ok(Some(StarknetContract::Legacy(class))), - CompiledClass::Class(_) => { - let class = ContractClassProvider::sierra_class(&state, class_hash)? - .map(StarknetContract::Sierra); - Ok(class) - } - } - } - - pub fn storage_at( - &self, - contract_address: ContractAddress, - storage_key: StorageKey, - block_id: BlockIdOrTag, - ) -> SequencerResult { - let state = self.state(&block_id)?; - - // check that contract exist by checking the class hash of the contract - let Some(_) = StateProvider::class_hash_of_contract(&state, contract_address)? else { - return Err(SequencerError::ContractNotFound(contract_address)); - }; - - let value = StateProvider::storage(&state, contract_address, storage_key)?; - Ok(value.unwrap_or_default()) - } - - pub fn chain_id(&self) -> ChainId { - self.backend.chain_id - } - - pub fn block_number(&self) -> SequencerResult { - let num = BlockNumberProvider::latest_number(&self.backend.blockchain.provider())?; - Ok(num) - } - - pub fn block_tx_count(&self, block_id: BlockIdOrTag) -> SequencerResult> { - let provider = self.backend.blockchain.provider(); - - let count = match block_id { - BlockIdOrTag::Tag(BlockTag::Pending) => match self.pending_executor() { - Some(exec) => Some(exec.read().transactions().len() as u64), - - None => { - let hash = BlockHashProvider::latest_hash(provider)?; - TransactionProvider::transaction_count_by_block(provider, hash.into())? - } - }, - - BlockIdOrTag::Tag(BlockTag::Latest) => { - let num = BlockNumberProvider::latest_number(provider)?; - TransactionProvider::transaction_count_by_block(provider, num.into())? - } - - BlockIdOrTag::Number(num) => { - TransactionProvider::transaction_count_by_block(provider, num.into())? - } - - BlockIdOrTag::Hash(hash) => { - TransactionProvider::transaction_count_by_block(provider, hash.into())? - } - }; - - Ok(count) - } - - pub fn nonce_at( - &self, - block_id: BlockIdOrTag, - contract_address: ContractAddress, - ) -> SequencerResult> { - let state = self.state(&block_id)?; - let nonce = StateProvider::nonce(&state, contract_address)?; - Ok(nonce) + pub fn pool(&self) -> &Arc { + &self.pool } - pub fn transaction(&self, hash: &TxHash) -> SequencerResult> { - let tx = - TransactionProvider::transaction_by_hash(self.backend.blockchain.provider(), *hash)?; - - let tx @ Some(_) = tx else { - return Ok(self.pending_executor().as_ref().and_then(|exec| { - exec.read() - .transactions() - .iter() - .find_map(|tx| if tx.0.hash == *hash { Some(tx.0.clone()) } else { None }) - })); - }; - - Ok(tx) + pub fn config(&self) -> &SequencerConfig { + &self.config } - pub fn events( - &self, - from_block: BlockIdOrTag, - to_block: BlockIdOrTag, - address: Option, - keys: Option>>, - continuation_token: Option, - chunk_size: u64, - ) -> SequencerResult { - let provider = self.backend.blockchain.provider(); - let mut current_block = 0; - - let (mut from_block, to_block) = { - let from = BlockIdReader::convert_block_id(provider, from_block)? - .ok_or(SequencerError::BlockNotFound(to_block))?; - let to = BlockIdReader::convert_block_id(provider, to_block)? - .ok_or(SequencerError::BlockNotFound(to_block))?; - (from, to) - }; - - let mut continuation_token = match continuation_token { - Some(token) => ContinuationToken::parse(token)?, - None => ContinuationToken::default(), - }; - - // skip blocks that have been already read - from_block += continuation_token.block_n; - - let mut filtered_events = Vec::with_capacity(chunk_size as usize); - - for i in from_block..=to_block { - let block_hash = BlockHashProvider::block_hash_by_num(provider, i)? - .ok_or(SequencerError::BlockNotFound(BlockIdOrTag::Number(i)))?; - - let receipts = ReceiptProvider::receipts_by_block(provider, BlockHashOrNumber::Num(i))? - .ok_or(SequencerError::BlockNotFound(BlockIdOrTag::Number(i)))?; - - let tx_range = BlockProvider::block_body_indices(provider, BlockHashOrNumber::Num(i))? - .ok_or(SequencerError::BlockNotFound(BlockIdOrTag::Number(i)))?; - let tx_hashes = - TransactionsProviderExt::transaction_hashes_in_range(provider, tx_range.into())?; - - let txn_n = receipts.len(); - if (txn_n as u64) < continuation_token.txn_n { - return Err(SequencerError::ContinuationToken( - ContinuationTokenError::InvalidToken, - )); - } - - for (tx_hash, events) in tx_hashes - .into_iter() - .zip(receipts.iter().map(|r| r.events())) - .skip(continuation_token.txn_n as usize) - { - let txn_events_len: usize = events.len(); - - // check if continuation_token.event_n is correct - match (txn_events_len as u64).cmp(&continuation_token.event_n) { - Ordering::Greater => (), - Ordering::Less => { - return Err(SequencerError::ContinuationToken( - ContinuationTokenError::InvalidToken, - )); - } - Ordering::Equal => { - continuation_token.txn_n += 1; - continuation_token.event_n = 0; - continue; - } - } - - // skip events - let txn_events = events.iter().skip(continuation_token.event_n as usize); - - let (new_filtered_events, continuation_index) = filter_events_by_params( - txn_events, - address, - keys.clone(), - Some((chunk_size as usize) - filtered_events.len()), - ); - - filtered_events.extend(new_filtered_events.iter().map(|e| EmittedEvent { - from_address: e.from_address.into(), - keys: e.keys.clone(), - data: e.data.clone(), - block_hash: Some(block_hash), - block_number: Some(i), - transaction_hash: tx_hash, - })); - - if filtered_events.len() >= chunk_size as usize { - let token = if current_block < to_block - || continuation_token.txn_n < txn_n as u64 - 1 - || continuation_index < txn_events_len - { - continuation_token.event_n = continuation_index as u64; - Some(continuation_token.to_string()) - } else { - None - }; - return Ok(EventsPage { events: filtered_events, continuation_token: token }); - } - - continuation_token.txn_n += 1; - continuation_token.event_n = 0; - } - - current_block += 1; - continuation_token.block_n += 1; - continuation_token.txn_n = 0; - } - - Ok(EventsPage { events: filtered_events, continuation_token: None }) - } - - pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { - if self.has_pending_transactions() { - return Err(SequencerError::PendingTransactions); - } - self.backend().block_context_generator.write().next_block_start_time = timestamp; - Ok(()) - } - - pub fn increase_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { - if self.has_pending_transactions() { - return Err(SequencerError::PendingTransactions); - } - self.backend().block_context_generator.write().block_timestamp_offset += timestamp as i64; - Ok(()) - } - - pub fn has_pending_transactions(&self) -> bool { - if let Some(ref exec) = self.pending_executor() { - !exec.read().transactions().is_empty() - } else { - false - } + pub fn provider(&self) -> &BlockchainProvider> { + self.backend.blockchain.provider() } // pub async fn set_storage_at( @@ -446,56 +110,6 @@ impl KatanaSequencer { // } } -fn filter_events_by_params( - events: Skip>, - address: Option, - filter_keys: Option>>, - max_results: Option, -) -> (Vec, usize) { - let mut filtered_events = vec![]; - let mut index = 0; - - // Iterate on block events. - for event in events { - index += 1; - if !address.map_or(true, |addr| addr == event.from_address) { - continue; - } - - let match_keys = match filter_keys { - // From starknet-api spec: - // Per key (by position), designate the possible values to be matched for events to be - // returned. Empty array designates 'any' value" - Some(ref filter_keys) => filter_keys.iter().enumerate().all(|(i, keys)| { - // Lets say we want to filter events which are either named `Event1` or `Event2` and - // custom key `0x1` or `0x2` Filter: [[sn_keccack("Event1"), - // sn_keccack("Event2")], ["0x1", "0x2"]] - - // This checks: number of keys in event >= number of keys in filter (we check > i - // and not >= i because i is zero indexed) because otherwise this - // event doesn't contain all the keys we requested - event.keys.len() > i && - // This checks: Empty array desginates 'any' value - (keys.is_empty() - || - // This checks: If this events i'th value is one of the requested value in filter_keys[i] - keys.contains(&event.keys[i])) - }), - None => true, - }; - - if match_keys { - filtered_events.push(event.clone()); - if let Some(max_results) = max_results { - if filtered_events.len() >= max_results { - break; - } - } - } - } - (filtered_events, index) -} - #[cfg(test)] mod tests { use katana_executor::implementation::noop::NoopExecutorFactory; diff --git a/crates/katana/core/src/sequencer_error.rs b/crates/katana/core/src/sequencer_error.rs deleted file mode 100644 index cf84bffed6..0000000000 --- a/crates/katana/core/src/sequencer_error.rs +++ /dev/null @@ -1,20 +0,0 @@ -use katana_primitives::block::BlockIdOrTag; -use katana_primitives::contract::ContractAddress; -use katana_primitives::event::ContinuationTokenError; -use katana_provider::error::ProviderError; - -#[derive(Debug, thiserror::Error)] -pub enum SequencerError { - #[error("Block {0:?} not found.")] - BlockNotFound(BlockIdOrTag), - #[error("Contract address {0} not found.")] - ContractNotFound(ContractAddress), - #[error("State for block {0:?} not found.")] - StateNotFound(BlockIdOrTag), - #[error("Wait for pending transactions.")] - PendingTransactions, - #[error(transparent)] - ContinuationToken(#[from] ContinuationTokenError), - #[error(transparent)] - Provider(#[from] ProviderError), -} diff --git a/crates/katana/core/tests/sequencer.rs b/crates/katana/core/tests/sequencer.rs deleted file mode 100644 index a50047eb47..0000000000 --- a/crates/katana/core/tests/sequencer.rs +++ /dev/null @@ -1,138 +0,0 @@ -use alloy_primitives::U256; -use katana_core::backend::config::StarknetConfig; -use katana_core::sequencer::{KatanaSequencer, SequencerConfig}; -use katana_executor::implementation::noop::NoopExecutorFactory; -use katana_primitives::genesis::allocation::DevAllocationsGenerator; -use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; -use katana_primitives::genesis::Genesis; -use katana_provider::traits::block::{BlockNumberProvider, BlockProvider}; -use katana_provider::traits::env::BlockEnvProvider; - -fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { - let accounts = DevAllocationsGenerator::new(2) - .with_balance(U256::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE)) - .generate(); - - let mut genesis = Genesis::default(); - genesis.extend_allocations(accounts.into_iter().map(|(k, v)| (k, v.into()))); - - ( - SequencerConfig::default(), - StarknetConfig { genesis, disable_fee: true, ..Default::default() }, - ) -} - -async fn create_test_sequencer() -> KatanaSequencer { - let executor_factory = NoopExecutorFactory::new(); - let (sequencer_config, starknet_config) = create_test_sequencer_config(); - KatanaSequencer::new(executor_factory, sequencer_config, starknet_config).await.unwrap() -} - -#[tokio::test] -async fn test_next_block_timestamp_in_past() { - let sequencer = create_test_sequencer().await; - let provider = sequencer.backend.blockchain.provider(); - - let block_num = provider.latest_number().unwrap(); - let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap(); - sequencer.backend.update_block_env(&mut block_env); - let block1 = sequencer.backend.mine_empty_block(&block_env).unwrap().block_number; - - let block1_timestamp = - BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; - - sequencer.set_next_block_timestamp(block1_timestamp - 1000).unwrap(); - - let block_num = provider.latest_number().unwrap(); - let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap(); - sequencer.backend.update_block_env(&mut block_env); - let block2 = sequencer.backend.mine_empty_block(&block_env).unwrap().block_number; - - let block2_timestamp = - BlockProvider::block(provider, block2.into()).unwrap().unwrap().header.timestamp; - - assert_eq!(block2_timestamp, block1_timestamp - 1000, "timestamp should be updated"); -} - -#[tokio::test] -async fn test_set_next_block_timestamp_in_future() { - let sequencer = create_test_sequencer().await; - let provider = sequencer.backend.blockchain.provider(); - - let block_num = provider.latest_number().unwrap(); - let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap(); - sequencer.backend.update_block_env(&mut block_env); - let block1 = sequencer.backend.mine_empty_block(&block_env).unwrap().block_number; - - let block1_timestamp = - BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; - - sequencer.set_next_block_timestamp(block1_timestamp + 1000).unwrap(); - - let block_num = provider.latest_number().unwrap(); - let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap(); - sequencer.backend.update_block_env(&mut block_env); - let block2 = sequencer.backend.mine_empty_block(&block_env).unwrap().block_number; - - let block2_timestamp = - BlockProvider::block(provider, block2.into()).unwrap().unwrap().header.timestamp; - - assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); -} - -#[tokio::test] -async fn test_increase_next_block_timestamp() { - let sequencer = create_test_sequencer().await; - let provider = sequencer.backend.blockchain.provider(); - - let block_num = provider.latest_number().unwrap(); - let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap(); - sequencer.backend.update_block_env(&mut block_env); - let block1 = sequencer.backend.mine_empty_block(&block_env).unwrap().block_number; - - let block1_timestamp = - BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; - - sequencer.increase_next_block_timestamp(1000).unwrap(); - - let block_num = provider.latest_number().unwrap(); - let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap(); - sequencer.backend.update_block_env(&mut block_env); - let block2 = sequencer.backend.mine_empty_block(&block_env).unwrap().block_number; - - let block2_timestamp = - BlockProvider::block(provider, block2.into()).unwrap().unwrap().header.timestamp; - - // Depending on the current time and the machine we run on, we may have 1 sec difference - // between the expected and actual timestamp. - // We take this possible delay in account to have the test more robust for now, - // but it may due to how the timestamp is updated in the sequencer. - assert!( - block2_timestamp == block1_timestamp + 1000 || block2_timestamp == block1_timestamp + 1001, - "timestamp should be updated" - ); -} - -// #[tokio::test] -// async fn test_set_storage_at_on_instant_mode() { -// let sequencer = create_test_sequencer().await; -// sequencer.backend.mine_empty_block(); - -// let contract_address = ContractAddress(patricia_key!("0x1337")); -// let key = StorageKey(patricia_key!("0x20")); -// let val = stark_felt!("0xABC"); - -// { -// let mut state = sequencer.backend.state.write().await; -// let read_val = state.get_storage_at(contract_address, key).unwrap(); -// assert_eq!(stark_felt!("0x0"), read_val, "latest storage value should be 0"); -// } - -// sequencer.set_storage_at(contract_address, key, val).await.unwrap(); - -// { -// let mut state = sequencer.backend.state.write().await; -// let read_val = state.get_storage_at(contract_address, key).unwrap(); -// assert_eq!(val, read_val, "latest storage value incorrect after generate"); -// } -// } diff --git a/crates/katana/rpc/rpc-types/src/error/dev.rs b/crates/katana/rpc/rpc-types/src/error/dev.rs new file mode 100644 index 0000000000..78126bf9ba --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/error/dev.rs @@ -0,0 +1,16 @@ +use jsonrpsee::core::Error; +use jsonrpsee::types::error::CallError; +use jsonrpsee::types::ErrorObject; + +#[derive(thiserror::Error, Clone, Copy, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum DevApiError { + #[error("Wait for pending transactions.")] + PendingTransactions, +} + +impl From for Error { + fn from(err: DevApiError) -> Self { + Error::Call(CallError::Custom(ErrorObject::owned(err as i32, err.to_string(), None::<()>))) + } +} diff --git a/crates/katana/rpc/rpc-types/src/error/mod.rs b/crates/katana/rpc/rpc-types/src/error/mod.rs index e935e90516..725e3e7576 100644 --- a/crates/katana/rpc/rpc-types/src/error/mod.rs +++ b/crates/katana/rpc/rpc-types/src/error/mod.rs @@ -1,3 +1,4 @@ +pub mod dev; pub mod katana; pub mod saya; pub mod starknet; diff --git a/crates/katana/rpc/rpc-types/src/error/saya.rs b/crates/katana/rpc/rpc-types/src/error/saya.rs index b832c46361..48c7b16ee7 100644 --- a/crates/katana/rpc/rpc-types/src/error/saya.rs +++ b/crates/katana/rpc/rpc-types/src/error/saya.rs @@ -1,7 +1,6 @@ use jsonrpsee::core::Error; use jsonrpsee::types::error::CallError; use jsonrpsee::types::ErrorObject; -use katana_core::sequencer_error::SequencerError; use katana_provider::error::ProviderError; #[derive(Debug, thiserror::Error, Clone)] @@ -34,15 +33,6 @@ impl From for SayaApiError { } } -impl From for SayaApiError { - fn from(value: SequencerError) -> Self { - match value { - SequencerError::BlockNotFound(_) => SayaApiError::BlockNotFound, - err => SayaApiError::UnexpectedError { reason: err.to_string() }, - } - } -} - impl From for Error { fn from(err: SayaApiError) -> Self { let code = err.code(); diff --git a/crates/katana/rpc/rpc-types/src/error/starknet.rs b/crates/katana/rpc/rpc-types/src/error/starknet.rs index a4aacae381..9a5d32ed68 100644 --- a/crates/katana/rpc/rpc-types/src/error/starknet.rs +++ b/crates/katana/rpc/rpc-types/src/error/starknet.rs @@ -1,7 +1,7 @@ use jsonrpsee::core::Error; use jsonrpsee::types::error::CallError; use jsonrpsee::types::ErrorObject; -use katana_core::sequencer_error::SequencerError; +use katana_primitives::event::ContinuationTokenError; use katana_provider::error::ProviderError; use serde::Serialize; @@ -138,16 +138,23 @@ impl From for StarknetApiError { } } -impl From for StarknetApiError { - fn from(value: SequencerError) -> Self { +impl From for StarknetApiError { + fn from(value: ContinuationTokenError) -> Self { match value { - SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, - err => StarknetApiError::UnexpectedError { reason: err.to_string() }, + ContinuationTokenError::InvalidToken => StarknetApiError::InvalidContinuationToken, + ContinuationTokenError::ParseFailed(e) => { + StarknetApiError::UnexpectedError { reason: e.to_string() } + } } } } +impl From for StarknetApiError { + fn from(value: anyhow::Error) -> Self { + StarknetApiError::UnexpectedError { reason: value.to_string() } + } +} + #[cfg(test)] mod tests { use rstest::rstest; diff --git a/crates/katana/rpc/rpc-types/src/error/torii.rs b/crates/katana/rpc/rpc-types/src/error/torii.rs index c37f24e067..51459497f8 100644 --- a/crates/katana/rpc/rpc-types/src/error/torii.rs +++ b/crates/katana/rpc/rpc-types/src/error/torii.rs @@ -2,7 +2,6 @@ use futures::channel::mpsc::Receiver; use jsonrpsee::core::Error; use jsonrpsee::types::error::CallError; use jsonrpsee::types::ErrorObject; -use katana_core::sequencer_error::SequencerError; use katana_core::service::block_producer::TxWithOutcome; use katana_provider::error::ProviderError; @@ -47,15 +46,6 @@ impl From for ToriiApiError { } } -impl From for ToriiApiError { - fn from(value: SequencerError) -> Self { - match value { - SequencerError::BlockNotFound(_) => ToriiApiError::BlockNotFound, - err => ToriiApiError::UnexpectedError { reason: err.to_string() }, - } - } -} - impl From for Error { fn from(err: ToriiApiError) -> Self { let code = err.code(); diff --git a/crates/katana/rpc/rpc/Cargo.toml b/crates/katana/rpc/rpc/Cargo.toml index d50fbe53fb..16734f5d53 100644 --- a/crates/katana/rpc/rpc/Cargo.toml +++ b/crates/katana/rpc/rpc/Cargo.toml @@ -29,6 +29,7 @@ tracing.workspace = true [dev-dependencies] alloy = { git = "https://github.com/alloy-rs/alloy", features = [ "contract", "network", "node-bindings", "provider-http", "providers", "signer-local" ] } +alloy-primitives = { workspace = true, features = [ "serde" ] } assert_matches.workspace = true cainome.workspace = true dojo-test-utils.workspace = true diff --git a/crates/katana/rpc/rpc/src/dev.rs b/crates/katana/rpc/rpc/src/dev.rs index db7a189984..1e70a695fc 100644 --- a/crates/katana/rpc/rpc/src/dev.rs +++ b/crates/katana/rpc/rpc/src/dev.rs @@ -5,7 +5,7 @@ use katana_core::sequencer::KatanaSequencer; use katana_executor::ExecutorFactory; use katana_primitives::FieldElement; use katana_rpc_api::dev::DevApiServer; -use katana_rpc_types::error::katana::KatanaApiError; +use katana_rpc_types::error::dev::DevApiError; #[allow(missing_debug_implementations)] pub struct DevApi { @@ -16,6 +16,36 @@ impl DevApi { pub fn new(sequencer: Arc>) -> Self { Self { sequencer } } + + fn has_pending_transactions(&self) -> bool { + if let Some(ref exec) = self.sequencer.pending_executor() { + !exec.read().transactions().is_empty() + } else { + false + } + } + + pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), DevApiError> { + if self.has_pending_transactions() { + return Err(DevApiError::PendingTransactions); + } + + let mut block_context_generator = self.sequencer.backend().block_context_generator.write(); + block_context_generator.next_block_start_time = timestamp; + + Ok(()) + } + + pub fn increase_next_block_timestamp(&self, offset: u64) -> Result<(), DevApiError> { + if self.has_pending_transactions() { + return Err(DevApiError::PendingTransactions); + } + + let mut block_context_generator = self.sequencer.backend().block_context_generator.write(); + block_context_generator.block_timestamp_offset += offset as i64; + + Ok(()) + } } #[async_trait] @@ -31,15 +61,11 @@ impl DevApiServer for DevApi { } async fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), Error> { - self.sequencer - .set_next_block_timestamp(timestamp) - .map_err(|_| Error::from(KatanaApiError::FailedToChangeNextBlockTimestamp)) + Ok(self.set_next_block_timestamp(timestamp)?) } async fn increase_next_block_timestamp(&self, timestamp: u64) -> Result<(), Error> { - self.sequencer - .increase_next_block_timestamp(timestamp) - .map_err(|_| Error::from(KatanaApiError::FailedToChangeNextBlockTimestamp)) + Ok(self.increase_next_block_timestamp(timestamp)?) } async fn set_storage_at( diff --git a/crates/katana/rpc/rpc/src/saya.rs b/crates/katana/rpc/rpc/src/saya.rs index b6e6f7da5b..6cda6ddc32 100644 --- a/crates/katana/rpc/rpc/src/saya.rs +++ b/crates/katana/rpc/rpc/src/saya.rs @@ -45,7 +45,7 @@ impl SayaApiServer for SayaApi { block_id: BlockIdOrTag, ) -> RpcResult> { self.on_io_blocking_task(move |this| { - let provider = this.sequencer.backend.blockchain.provider(); + let provider = this.sequencer.backend().blockchain.provider(); match block_id { BlockIdOrTag::Tag(BlockTag::Pending) => { diff --git a/crates/katana/rpc/rpc/src/starknet/mod.rs b/crates/katana/rpc/rpc/src/starknet/mod.rs index 730a979fec..c7db8cb5d6 100644 --- a/crates/katana/rpc/rpc/src/starknet/mod.rs +++ b/crates/katana/rpc/rpc/src/starknet/mod.rs @@ -4,15 +4,40 @@ mod read; mod trace; mod write; +use std::cmp::Ordering; +use std::iter::Skip; +use std::slice::Iter; use std::sync::Arc; +use anyhow::Result; use katana_core::sequencer::KatanaSequencer; -use katana_executor::ExecutorFactory; -use katana_primitives::block::BlockIdOrTag; -use katana_primitives::transaction::ExecutableTxWithHash; +use katana_executor::{ExecutionResult, ExecutorFactory}; +use katana_primitives::block::{ + BlockHash, BlockHashOrNumber, BlockIdOrTag, BlockNumber, BlockTag, FinalityStatus, +}; +use katana_primitives::class::{ClassHash, CompiledClass}; +use katana_primitives::contract::{ContractAddress, Nonce, StorageKey, StorageValue}; +use katana_primitives::conversion::rpc::legacy_inner_to_rpc_class; +use katana_primitives::env::BlockEnv; +use katana_primitives::event::ContinuationToken; +use katana_primitives::receipt::Event; +use katana_primitives::transaction::{ExecutableTxWithHash, TxHash, TxWithHash}; +use katana_primitives::FieldElement; +use katana_provider::traits::block::{ + BlockHashProvider, BlockIdReader, BlockNumberProvider, BlockProvider, +}; +use katana_provider::traits::contract::ContractClassProvider; +use katana_provider::traits::env::BlockEnvProvider; +use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; +use katana_provider::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionsProviderExt, +}; use katana_rpc_types::error::starknet::StarknetApiError; use katana_rpc_types::FeeEstimate; use katana_tasks::{BlockingTaskPool, TokioTaskSpawner}; +use starknet::core::types::{ + ContractClass, EmittedEvent, EventsPage, TransactionExecutionStatus, TransactionStatus, +}; #[allow(missing_debug_implementations)] pub struct StarknetApi { @@ -34,7 +59,8 @@ impl StarknetApi { pub fn new(sequencer: Arc>) -> Self { let blocking_task_pool = BlockingTaskPool::new().expect("failed to create blocking task pool"); - Self { inner: Arc::new(Inner { sequencer, blocking_task_pool }) } + let inner = Inner { sequencer, blocking_task_pool }; + Self { inner: Arc::new(inner) } } async fn on_cpu_blocking_task(&self, func: F) -> T @@ -61,16 +87,13 @@ impl StarknetApi { block_id: BlockIdOrTag, flags: katana_executor::SimulationFlag, ) -> Result, StarknetApiError> { - let sequencer = &self.inner.sequencer; // get the state and block env at the specified block for execution - let state = sequencer.state(&block_id).map_err(StarknetApiError::from)?; - let env = sequencer - .block_env_at(block_id) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::BlockNotFound)?; + let state = self.state(&block_id)?; + let env = self.block_env_at(&block_id)?; // create the executor - let executor = sequencer.backend.executor_factory.with_state_and_block_env(state, env); + let executor = + self.inner.sequencer.backend().executor_factory.with_state_and_block_env(state, env); let results = executor.estimate_fee(transactions, flags); let mut estimates = Vec::with_capacity(results.len()); @@ -96,4 +119,404 @@ impl StarknetApi { Ok(estimates) } + + fn state(&self, block_id: &BlockIdOrTag) -> Result, StarknetApiError> { + let provider = self.inner.sequencer.backend().blockchain.provider(); + + let state = match block_id { + BlockIdOrTag::Tag(BlockTag::Latest) => Some(provider.latest()?), + + BlockIdOrTag::Tag(BlockTag::Pending) => { + if let Some(exec) = self.inner.sequencer.pending_executor() { + Some(exec.read().state()) + } else { + Some(provider.latest()?) + } + } + + BlockIdOrTag::Hash(hash) => provider.historical((*hash).into())?, + BlockIdOrTag::Number(num) => provider.historical((*num).into())?, + }; + + state.ok_or(StarknetApiError::BlockNotFound) + } + + fn block_env_at(&self, block_id: &BlockIdOrTag) -> Result { + let provider = self.inner.sequencer.backend().blockchain.provider(); + + let env = match block_id { + BlockIdOrTag::Tag(BlockTag::Pending) => { + if let Some(exec) = self.inner.sequencer.pending_executor() { + Some(exec.read().block_env()) + } else { + let num = provider.latest_number()?; + provider.block_env_at(num.into())? + } + } + + BlockIdOrTag::Tag(BlockTag::Latest) => { + let num = provider.latest_number()?; + provider.block_env_at(num.into())? + } + + BlockIdOrTag::Hash(hash) => provider.block_env_at((*hash).into())?, + BlockIdOrTag::Number(num) => provider.block_env_at((*num).into())?, + }; + + env.ok_or(StarknetApiError::BlockNotFound) + } + + fn block_hash_and_number(&self) -> Result<(BlockHash, BlockNumber), StarknetApiError> { + let provider = self.inner.sequencer.backend().blockchain.provider(); + let hash = provider.latest_hash()?; + let number = provider.latest_number()?; + Ok((hash, number)) + } + + async fn class_at_hash( + &self, + block_id: BlockIdOrTag, + class_hash: ClassHash, + ) -> Result { + self.on_io_blocking_task(move |this| { + let state = this.state(&block_id)?; + + let Some(class) = state.class(class_hash)? else { + return Err(StarknetApiError::ClassHashNotFound); + }; + + match class { + CompiledClass::Deprecated(class) => Ok(legacy_inner_to_rpc_class(class)?), + CompiledClass::Class(_) => { + let Some(sierra) = state.sierra_class(class_hash)? else { + return Err(StarknetApiError::UnexpectedError { + reason: "Class hash exist, but its Sierra class is missing".to_string(), + }); + }; + + Ok(ContractClass::Sierra(sierra)) + } + } + }) + .await + } + + async fn class_hash_at_address( + &self, + block_id: BlockIdOrTag, + contract_address: ContractAddress, + ) -> Result { + self.on_io_blocking_task(move |this| { + let state = this.state(&block_id)?; + let class_hash = state.class_hash_of_contract(contract_address)?; + class_hash.ok_or(StarknetApiError::ContractNotFound) + }) + .await + } + + async fn class_at_address( + &self, + block_id: BlockIdOrTag, + contract_address: ContractAddress, + ) -> Result { + let hash = self.class_hash_at_address(block_id, contract_address).await?; + let class = self.class_at_hash(block_id, hash).await?; + Ok(class) + } + + fn storage_at( + &self, + contract_address: ContractAddress, + storage_key: StorageKey, + block_id: BlockIdOrTag, + ) -> Result { + let state = self.state(&block_id)?; + + // check that contract exist by checking the class hash of the contract + let Some(_) = state.class_hash_of_contract(contract_address)? else { + return Err(StarknetApiError::ContractNotFound); + }; + + let value = state.storage(contract_address, storage_key)?; + Ok(value.unwrap_or_default()) + } + + fn block_tx_count(&self, block_id: BlockIdOrTag) -> Result { + let provider = self.inner.sequencer.backend().blockchain.provider(); + + let block_id: BlockHashOrNumber = match block_id { + BlockIdOrTag::Tag(BlockTag::Pending) => match self.inner.sequencer.pending_executor() { + Some(exec) => return Ok(exec.read().transactions().len() as u64), + None => provider.latest_hash()?.into(), + }, + BlockIdOrTag::Tag(BlockTag::Latest) => provider.latest_number()?.into(), + BlockIdOrTag::Number(num) => num.into(), + BlockIdOrTag::Hash(hash) => hash.into(), + }; + + let count = provider + .transaction_count_by_block(block_id)? + .ok_or(StarknetApiError::BlockNotFound)?; + + Ok(count) + } + + async fn latest_block_number(&self) -> Result { + self.on_io_blocking_task(move |this| { + Ok(this.inner.sequencer.backend().blockchain.provider().latest_number()?) + }) + .await + } + + async fn nonce_at( + &self, + block_id: BlockIdOrTag, + contract_address: ContractAddress, + ) -> Result { + self.on_io_blocking_task(move |this| { + let state = this.state(&block_id)?; + let nonce = state.nonce(contract_address)?.ok_or(StarknetApiError::ContractNotFound)?; + Ok(nonce) + }) + .await + } + + async fn transaction(&self, hash: TxHash) -> Result { + self.on_io_blocking_task(move |this| { + let tx = + this.inner.sequencer.backend().blockchain.provider().transaction_by_hash(hash)?; + + let tx = match tx { + tx @ Some(_) => tx, + None => { + // check if the transaction is in the pending block + this.inner.sequencer.pending_executor().as_ref().and_then(|exec| { + exec.read() + .transactions() + .iter() + .find(|(tx, _)| tx.hash == hash) + .map(|(tx, _)| tx.clone()) + }) + } + }; + + tx.ok_or(StarknetApiError::TxnHashNotFound) + }) + .await + } + + fn events( + &self, + from_block: BlockIdOrTag, + to_block: BlockIdOrTag, + address: Option, + keys: Option>>, + continuation_token: Option, + chunk_size: u64, + ) -> Result { + let provider = self.inner.sequencer.backend().blockchain.provider(); + let mut current_block = 0; + + let mut from = + provider.convert_block_id(from_block)?.ok_or(StarknetApiError::BlockNotFound)?; + let to = provider.convert_block_id(to_block)?.ok_or(StarknetApiError::BlockNotFound)?; + + let mut continuation_token = match continuation_token { + Some(token) => ContinuationToken::parse(token)?, + None => ContinuationToken::default(), + }; + + // skip blocks that have been already read + from += continuation_token.block_n; + + let mut filtered_events = Vec::with_capacity(chunk_size as usize); + + for i in from..=to { + let block_hash = + provider.block_hash_by_num(i)?.ok_or(StarknetApiError::BlockNotFound)?; + + let receipts = + provider.receipts_by_block(i.into())?.ok_or(StarknetApiError::BlockNotFound)?; + + let tx_range = + provider.block_body_indices(i.into())?.ok_or(StarknetApiError::BlockNotFound)?; + + let tx_hashes = provider.transaction_hashes_in_range(tx_range.into())?; + + let txn_n = receipts.len(); + if (txn_n as u64) < continuation_token.txn_n { + return Err(StarknetApiError::InvalidContinuationToken); + } + + for (tx_hash, events) in tx_hashes + .into_iter() + .zip(receipts.iter().map(|r| r.events())) + .skip(continuation_token.txn_n as usize) + { + let txn_events_len: usize = events.len(); + + // check if continuation_token.event_n is correct + match (txn_events_len as u64).cmp(&continuation_token.event_n) { + Ordering::Greater => (), + Ordering::Less => { + return Err(StarknetApiError::InvalidContinuationToken); + } + Ordering::Equal => { + continuation_token.txn_n += 1; + continuation_token.event_n = 0; + continue; + } + } + + // skip events + let txn_events = events.iter().skip(continuation_token.event_n as usize); + + let (new_filtered_events, continuation_index) = filter_events_by_params( + txn_events, + address, + keys.clone(), + Some((chunk_size as usize) - filtered_events.len()), + ); + + filtered_events.extend(new_filtered_events.iter().map(|e| EmittedEvent { + from_address: e.from_address.into(), + keys: e.keys.clone(), + data: e.data.clone(), + block_hash: Some(block_hash), + block_number: Some(i), + transaction_hash: tx_hash, + })); + + if filtered_events.len() >= chunk_size as usize { + let token = if current_block < to + || continuation_token.txn_n < txn_n as u64 - 1 + || continuation_index < txn_events_len + { + continuation_token.event_n = continuation_index as u64; + Some(continuation_token.to_string()) + } else { + None + }; + return Ok(EventsPage { events: filtered_events, continuation_token: token }); + } + + continuation_token.txn_n += 1; + continuation_token.event_n = 0; + } + + current_block += 1; + continuation_token.block_n += 1; + continuation_token.txn_n = 0; + } + + Ok(EventsPage { events: filtered_events, continuation_token: None }) + } + + async fn transaction_status( + &self, + hash: TxHash, + ) -> Result { + self.on_io_blocking_task(move |this| { + let provider = this.inner.sequencer.backend().blockchain.provider(); + let status = provider.transaction_status(hash)?; + + if let Some(status) = status { + // TODO: this might not work once we allow querying for 'failed' transactions from + // the provider + let Some(receipt) = provider.receipt_by_hash(hash)? else { + return Err(StarknetApiError::UnexpectedError { + reason: "Transaction hash exist, but the receipt is missing".to_string(), + }); + }; + + let exec_status = if receipt.is_reverted() { + TransactionExecutionStatus::Reverted + } else { + TransactionExecutionStatus::Succeeded + }; + + return Ok(match status { + FinalityStatus::AcceptedOnL1 => TransactionStatus::AcceptedOnL1(exec_status), + FinalityStatus::AcceptedOnL2 => TransactionStatus::AcceptedOnL2(exec_status), + }); + } + + // seach in the pending block if the transaction is not found + if let Some(pending_executor) = this.inner.sequencer.pending_executor() { + let pending_executor = pending_executor.read(); + let pending_txs = pending_executor.transactions(); + let (_, res) = pending_txs + .iter() + .find(|(tx, _)| tx.hash == hash) + .ok_or(StarknetApiError::TxnHashNotFound)?; + + // TODO: should impl From for TransactionStatus + let status = match res { + ExecutionResult::Failed { .. } => TransactionStatus::Rejected, + ExecutionResult::Success { receipt, .. } => { + if receipt.is_reverted() { + TransactionStatus::AcceptedOnL2(TransactionExecutionStatus::Reverted) + } else { + TransactionStatus::AcceptedOnL2(TransactionExecutionStatus::Succeeded) + } + } + }; + + Ok(status) + } else { + Err(StarknetApiError::TxnHashNotFound) + } + }) + .await + } +} + +fn filter_events_by_params( + events: Skip>, + address: Option, + filter_keys: Option>>, + max_results: Option, +) -> (Vec, usize) { + let mut filtered_events = vec![]; + let mut index = 0; + + // Iterate on block events. + for event in events { + index += 1; + if !address.map_or(true, |addr| addr == event.from_address) { + continue; + } + + let match_keys = match filter_keys { + // From starknet-api spec: + // Per key (by position), designate the possible values to be matched for events to be + // returned. Empty array designates 'any' value" + Some(ref filter_keys) => filter_keys.iter().enumerate().all(|(i, keys)| { + // Lets say we want to filter events which are either named `Event1` or `Event2` and + // custom key `0x1` or `0x2` Filter: [[sn_keccack("Event1"), + // sn_keccack("Event2")], ["0x1", "0x2"]] + + // This checks: number of keys in event >= number of keys in filter (we check > i + // and not >= i because i is zero indexed) because otherwise this + // event doesn't contain all the keys we requested + event.keys.len() > i && + // This checks: Empty array desginates 'any' value + (keys.is_empty() + || + // This checks: If this events i'th value is one of the requested value in filter_keys[i] + keys.contains(&event.keys[i])) + }), + None => true, + }; + + if match_keys { + filtered_events.push(event.clone()); + if let Some(max_results) = max_results { + if filtered_events.len() >= max_results { + break; + } + } + } + } + (filtered_events, index) } diff --git a/crates/katana/rpc/rpc/src/starknet/read.rs b/crates/katana/rpc/rpc/src/starknet/read.rs index 99c7d73367..9bf0a1c94d 100644 --- a/crates/katana/rpc/rpc/src/starknet/read.rs +++ b/crates/katana/rpc/rpc/src/starknet/read.rs @@ -1,15 +1,11 @@ use jsonrpsee::core::{async_trait, Error, RpcResult}; -use katana_core::backend::contract::StarknetContract; use katana_executor::{EntryPointCall, ExecutionResult, ExecutorFactory}; use katana_primitives::block::{BlockHashOrNumber, BlockIdOrTag, FinalityStatus, PartialHeader}; -use katana_primitives::conversion::rpc::legacy_inner_to_rpc_class; use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash, TxHash}; use katana_primitives::version::CURRENT_STARKNET_VERSION; use katana_primitives::FieldElement; use katana_provider::traits::block::{BlockHashProvider, BlockIdReader, BlockNumberProvider}; -use katana_provider::traits::transaction::{ - ReceiptProvider, TransactionProvider, TransactionStatusProvider, -}; +use katana_provider::traits::transaction::TransactionProvider; use katana_rpc_api::starknet::StarknetApiServer; use katana_rpc_types::block::{ BlockHashAndNumber, MaybePendingBlockWithReceipts, MaybePendingBlockWithTxHashes, @@ -26,14 +22,14 @@ use katana_rpc_types::{ ContractClass, FeeEstimate, FeltAsHex, FunctionCall, SimulationFlagForEstimateFee, }; use katana_rpc_types_builder::ReceiptBuilder; -use starknet::core::types::{BlockTag, TransactionExecutionStatus, TransactionStatus}; +use starknet::core::types::{BlockTag, TransactionStatus}; use super::StarknetApi; #[async_trait] impl StarknetApiServer for StarknetApi { async fn chain_id(&self) -> RpcResult { - Ok(FieldElement::from(self.inner.sequencer.chain_id()).into()) + Ok(self.inner.sequencer.backend().chain_id.id().into()) } async fn get_nonce( @@ -41,51 +37,19 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, contract_address: FieldElement, ) -> RpcResult { - self.on_io_blocking_task(move |this| { - let nonce = this - .inner - .sequencer - .nonce_at(block_id, contract_address.into()) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::ContractNotFound)?; - Ok(nonce.into()) - }) - .await + Ok(self.nonce_at(block_id, contract_address.into()).await?.into()) } async fn block_number(&self) -> RpcResult { - self.on_io_blocking_task(move |this| { - let block_number = - this.inner.sequencer.block_number().map_err(StarknetApiError::from)?; - Ok(block_number) - }) - .await + Ok(self.latest_block_number().await?) } async fn get_transaction_by_hash(&self, transaction_hash: FieldElement) -> RpcResult { - self.on_io_blocking_task(move |this| { - let tx = this - .inner - .sequencer - .transaction(&transaction_hash) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::TxnHashNotFound)?; - Ok(tx.into()) - }) - .await + Ok(self.transaction(transaction_hash).await?.into()) } async fn get_block_transaction_count(&self, block_id: BlockIdOrTag) -> RpcResult { - self.on_io_blocking_task(move |this| { - let count = this - .inner - .sequencer - .block_tx_count(block_id) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::BlockNotFound)?; - Ok(count) - }) - .await + self.on_io_blocking_task(move |this| Ok(this.block_tx_count(block_id)?)).await } async fn get_class_at( @@ -93,24 +57,15 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, contract_address: FieldElement, ) -> RpcResult { - let class_hash = self - .on_io_blocking_task(move |this| { - this.inner - .sequencer - .class_hash_at(block_id, contract_address.into()) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::ContractNotFound) - }) - .await?; - self.get_class(block_id, class_hash).await + Ok(self.class_at_address(block_id, contract_address.into()).await?) } async fn block_hash_and_number(&self) -> RpcResult { - let hash_and_num_pair = self - .on_io_blocking_task(move |this| this.inner.sequencer.block_hash_and_number()) - .await - .map_err(StarknetApiError::from)?; - Ok(hash_and_num_pair.into()) + self.on_io_blocking_task(move |this| { + let res = this.block_hash_and_number()?; + Ok(res.into()) + }) + .await } async fn get_block_with_tx_hashes( @@ -118,7 +73,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, ) -> RpcResult { self.on_io_blocking_task(move |this| { - let provider = this.inner.sequencer.backend.blockchain.provider(); + let provider = this.inner.sequencer.backend().blockchain.provider(); if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { if let Some(executor) = this.inner.sequencer.pending_executor() { @@ -185,7 +140,7 @@ impl StarknetApiServer for StarknetApi { let pending_txs = executor.transactions(); pending_txs.get(index as usize).map(|(tx, _)| tx.clone()) } else { - let provider = &this.inner.sequencer.backend.blockchain.provider(); + let provider = &this.inner.sequencer.backend().blockchain.provider(); let block_num = BlockIdReader::convert_block_id(provider, block_id) .map_err(StarknetApiError::from)? @@ -206,7 +161,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, ) -> RpcResult { self.on_io_blocking_task(move |this| { - let provider = this.inner.sequencer.backend.blockchain.provider(); + let provider = this.inner.sequencer.backend().blockchain.provider(); if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { if let Some(executor) = this.inner.sequencer.pending_executor() { @@ -263,7 +218,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, ) -> RpcResult { self.on_io_blocking_task(move |this| { - let provider = this.inner.sequencer.backend.blockchain.provider(); + let provider = this.inner.sequencer.backend().blockchain.provider(); if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { if let Some(executor) = this.inner.sequencer.pending_executor() { @@ -316,7 +271,7 @@ impl StarknetApiServer for StarknetApi { async fn get_state_update(&self, block_id: BlockIdOrTag) -> RpcResult { self.on_io_blocking_task(move |this| { - let provider = this.inner.sequencer.backend.blockchain.provider(); + let provider = this.inner.sequencer.backend().blockchain.provider(); let block_id = match block_id { BlockIdOrTag::Number(num) => BlockHashOrNumber::Num(num), @@ -344,7 +299,7 @@ impl StarknetApiServer for StarknetApi { transaction_hash: FieldElement, ) -> RpcResult { self.on_io_blocking_task(move |this| { - let provider = this.inner.sequencer.backend.blockchain.provider(); + let provider = this.inner.sequencer.backend().blockchain.provider(); let receipt = ReceiptBuilder::new(transaction_hash, provider) .build() .map_err(|e| StarknetApiError::UnexpectedError { reason: e.to_string() })?; @@ -388,16 +343,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, contract_address: FieldElement, ) -> RpcResult { - self.on_io_blocking_task(move |this| { - let hash = this - .inner - .sequencer - .class_hash_at(block_id, contract_address.into()) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::ContractNotFound)?; - Ok(hash.into()) - }) - .await + Ok(self.class_hash_at_address(block_id, contract_address.into()).await?.into()) } async fn get_class( @@ -405,21 +351,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, class_hash: FieldElement, ) -> RpcResult { - self.on_io_blocking_task(move |this| { - let class = - this.inner.sequencer.class(block_id, class_hash).map_err(StarknetApiError::from)?; - let Some(class) = class else { return Err(StarknetApiError::ClassHashNotFound.into()) }; - - match class { - StarknetContract::Legacy(c) => { - let contract = legacy_inner_to_rpc_class(c) - .map_err(|e| StarknetApiError::UnexpectedError { reason: e.to_string() })?; - Ok(contract) - } - StarknetContract::Sierra(c) => Ok(ContractClass::Sierra(c)), - } - }) - .await + Ok(self.class_at_hash(block_id, class_hash).await?) } async fn get_events(&self, filter: EventFilterWithPage) -> RpcResult { @@ -431,18 +363,14 @@ impl StarknetApiServer for StarknetApi { let keys = filter.event_filter.keys; let keys = keys.filter(|keys| !(keys.len() == 1 && keys.is_empty())); - let events = this - .inner - .sequencer - .events( - from_block, - to_block, - filter.event_filter.address.map(|f| f.into()), - keys, - filter.result_page_request.continuation_token, - filter.result_page_request.chunk_size, - ) - .map_err(StarknetApiError::from)?; + let events = this.events( + from_block, + to_block, + filter.event_filter.address.map(|f| f.into()), + keys, + filter.result_page_request.continuation_token, + filter.result_page_request.chunk_size, + )?; Ok(events) }) @@ -461,16 +389,15 @@ impl StarknetApiServer for StarknetApi { entry_point_selector: request.entry_point_selector, }; - let sequencer = &this.inner.sequencer; - // get the state and block env at the specified block for function call execution - let state = sequencer.state(&block_id).map_err(StarknetApiError::from)?; - let env = sequencer - .block_env_at(block_id) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::BlockNotFound)?; - - let executor = sequencer.backend.executor_factory.with_state_and_block_env(state, env); + let state = this.state(&block_id)?; + let env = this.block_env_at(&block_id)?; + let executor = this + .inner + .sequencer + .backend() + .executor_factory + .with_state_and_block_env(state, env); match executor.call(request) { Ok(retdata) => Ok(retdata.into_iter().map(|v| v.into()).collect()), @@ -489,12 +416,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, ) -> RpcResult { self.on_io_blocking_task(move |this| { - let value = this - .inner - .sequencer - .storage_at(contract_address.into(), key, block_id) - .map_err(StarknetApiError::from)?; - + let value = this.storage_at(contract_address.into(), key, block_id)?; Ok(value.into()) }) .await @@ -507,8 +429,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, ) -> RpcResult> { self.on_cpu_blocking_task(move |this| { - let sequencer = &this.inner.sequencer; - let chain_id = sequencer.chain_id(); + let chain_id = this.inner.sequencer.backend().chain_id; let transactions = request .into_iter() @@ -548,7 +469,7 @@ impl StarknetApiServer for StarknetApi { // If the node is run with transaction validation disabled, then we should not validate // transactions when estimating the fee even if the `SKIP_VALIDATE` flag is not set. let should_validate = - !(skip_validate || this.inner.sequencer.backend.config.disable_validate); + !(skip_validate || this.inner.sequencer.backend().config.disable_validate); let flags = katana_executor::SimulationFlag { skip_validate: !should_validate, ..Default::default() @@ -566,7 +487,7 @@ impl StarknetApiServer for StarknetApi { block_id: BlockIdOrTag, ) -> RpcResult { self.on_cpu_blocking_task(move |this| { - let chain_id = this.inner.sequencer.chain_id(); + let chain_id = this.inner.sequencer.backend().chain_id; let tx = message.into_tx_with_chain_id(chain_id); let hash = tx.calculate_hash(); @@ -597,55 +518,6 @@ impl StarknetApiServer for StarknetApi { &self, transaction_hash: TxHash, ) -> RpcResult { - self.on_io_blocking_task(move |this| { - let provider = this.inner.sequencer.backend.blockchain.provider(); - - let tx_status = - TransactionStatusProvider::transaction_status(provider, transaction_hash) - .map_err(StarknetApiError::from)?; - - if let Some(status) = tx_status { - if let Some(receipt) = ReceiptProvider::receipt_by_hash(provider, transaction_hash) - .map_err(StarknetApiError::from)? - { - let execution_status = if receipt.is_reverted() { - TransactionExecutionStatus::Reverted - } else { - TransactionExecutionStatus::Succeeded - }; - - return Ok(match status { - FinalityStatus::AcceptedOnL1 => { - TransactionStatus::AcceptedOnL1(execution_status) - } - FinalityStatus::AcceptedOnL2 => { - TransactionStatus::AcceptedOnL2(execution_status) - } - }); - } - } - - let pending_executor = - this.inner.sequencer.pending_executor().ok_or(StarknetApiError::TxnHashNotFound)?; - let pending_executor = pending_executor.read(); - - let pending_txs = pending_executor.transactions(); - let status = - pending_txs.iter().find(|(tx, _)| tx.hash == transaction_hash).map(|(_, res)| { - match res { - ExecutionResult::Failed { .. } => TransactionStatus::Rejected, - ExecutionResult::Success { receipt, .. } => { - TransactionStatus::AcceptedOnL2(if receipt.is_reverted() { - TransactionExecutionStatus::Reverted - } else { - TransactionExecutionStatus::Succeeded - }) - } - } - }); - - status.ok_or(Error::from(StarknetApiError::TxnHashNotFound)) - }) - .await + Ok(self.transaction_status(transaction_hash).await?) } } diff --git a/crates/katana/rpc/rpc/src/starknet/trace.rs b/crates/katana/rpc/rpc/src/starknet/trace.rs index 096e56a79a..8e557e4b2d 100644 --- a/crates/katana/rpc/rpc/src/starknet/trace.rs +++ b/crates/katana/rpc/rpc/src/starknet/trace.rs @@ -36,7 +36,7 @@ impl StarknetTraceApiServer for StarknetApi { simulation_flags: Vec, ) -> RpcResult> { self.on_cpu_blocking_task(move |this| { - let chain_id = this.inner.sequencer.chain_id(); + let chain_id = this.inner.sequencer.backend().chain_id; let executables = transactions .into_iter() @@ -74,11 +74,11 @@ impl StarknetTraceApiServer for StarknetApi { // If the node is run with transaction validation disabled, then we should not validate // even if the `SKIP_VALIDATE` flag is not set. let should_validate = !(simulation_flags.contains(&SimulationFlag::SkipValidate) - || this.inner.sequencer.backend.config.disable_validate); + || this.inner.sequencer.backend().config.disable_validate); // If the node is run with fee charge disabled, then we should disable charing fees even // if the `SKIP_FEE_CHARGE` flag is not set. let should_skip_fee = !(simulation_flags.contains(&SimulationFlag::SkipFeeCharge) - || this.inner.sequencer.backend.config.disable_fee); + || this.inner.sequencer.backend().config.disable_fee); let flags = katana_executor::SimulationFlag { skip_validate: !should_validate, @@ -88,14 +88,12 @@ impl StarknetTraceApiServer for StarknetApi { let sequencer = &this.inner.sequencer; // get the state and block env at the specified block for execution - let state = sequencer.state(&block_id).map_err(StarknetApiError::from)?; - let env = sequencer - .block_env_at(block_id) - .map_err(StarknetApiError::from)? - .ok_or(StarknetApiError::BlockNotFound)?; + let state = this.state(&block_id)?; + let env = this.block_env_at(&block_id)?; // create the executor - let executor = sequencer.backend.executor_factory.with_state_and_block_env(state, env); + let executor = + sequencer.backend().executor_factory.with_state_and_block_env(state, env); let results = executor.simulate(executables, flags); let mut simulated = Vec::with_capacity(results.len()); diff --git a/crates/katana/rpc/rpc/src/starknet/write.rs b/crates/katana/rpc/rpc/src/starknet/write.rs index 9d147fb292..286f9cf8d2 100644 --- a/crates/katana/rpc/rpc/src/starknet/write.rs +++ b/crates/katana/rpc/rpc/src/starknet/write.rs @@ -10,77 +10,91 @@ use katana_rpc_types::transaction::{ use super::StarknetApi; -#[async_trait] -impl StarknetWriteApiServer for StarknetApi { - async fn add_invoke_transaction( +impl StarknetApi { + async fn add_invoke_transaction_impl( &self, - invoke_transaction: BroadcastedInvokeTx, - ) -> RpcResult { + tx: BroadcastedInvokeTx, + ) -> Result { self.on_io_blocking_task(move |this| { - if invoke_transaction.is_query() { - return Err(StarknetApiError::UnsupportedTransactionVersion.into()); + if tx.is_query() { + return Err(StarknetApiError::UnsupportedTransactionVersion); } - let chain_id = this.inner.sequencer.chain_id(); - - let tx = invoke_transaction.into_tx_with_chain_id(chain_id); + let tx = tx.into_tx_with_chain_id(this.inner.sequencer.backend().chain_id); let tx = ExecutableTxWithHash::new(ExecutableTx::Invoke(tx)); let tx_hash = tx.hash; - this.inner.sequencer.add_transaction_to_pool(tx); - + this.inner.sequencer.pool().add_transaction(tx); Ok(tx_hash.into()) }) .await } - async fn add_declare_transaction( + async fn add_declare_transaction_impl( &self, - declare_transaction: BroadcastedDeclareTx, - ) -> RpcResult { + tx: BroadcastedDeclareTx, + ) -> Result { self.on_io_blocking_task(move |this| { - if declare_transaction.is_query() { - return Err(StarknetApiError::UnsupportedTransactionVersion.into()); + if tx.is_query() { + return Err(StarknetApiError::UnsupportedTransactionVersion); } - let chain_id = this.inner.sequencer.chain_id(); - - let tx = declare_transaction - .try_into_tx_with_chain_id(chain_id) + let tx = tx + .try_into_tx_with_chain_id(this.inner.sequencer.backend().chain_id) .map_err(|_| StarknetApiError::InvalidContractClass)?; let class_hash = tx.class_hash(); let tx = ExecutableTxWithHash::new(ExecutableTx::Declare(tx)); let tx_hash = tx.hash; - this.inner.sequencer.add_transaction_to_pool(tx); - + this.inner.sequencer.pool().add_transaction(tx); Ok((tx_hash, class_hash).into()) }) .await } - async fn add_deploy_account_transaction( + async fn add_deploy_account_transaction_impl( &self, - deploy_account_transaction: BroadcastedDeployAccountTx, - ) -> RpcResult { + tx: BroadcastedDeployAccountTx, + ) -> Result { self.on_io_blocking_task(move |this| { - if deploy_account_transaction.is_query() { - return Err(StarknetApiError::UnsupportedTransactionVersion.into()); + if tx.is_query() { + return Err(StarknetApiError::UnsupportedTransactionVersion); } - let chain_id = this.inner.sequencer.chain_id(); - - let tx = deploy_account_transaction.into_tx_with_chain_id(chain_id); + let tx = tx.into_tx_with_chain_id(this.inner.sequencer.backend().chain_id); let contract_address = tx.contract_address(); let tx = ExecutableTxWithHash::new(ExecutableTx::DeployAccount(tx)); let tx_hash = tx.hash; - this.inner.sequencer.add_transaction_to_pool(tx); - + this.inner.sequencer.pool().add_transaction(tx); Ok((tx_hash, contract_address).into()) }) .await } } + +#[async_trait] +impl StarknetWriteApiServer for StarknetApi { + async fn add_invoke_transaction( + &self, + invoke_transaction: BroadcastedInvokeTx, + ) -> RpcResult { + Ok(self.add_invoke_transaction_impl(invoke_transaction).await?) + } + + async fn add_declare_transaction( + &self, + declare_transaction: BroadcastedDeclareTx, + ) -> RpcResult { + Ok(self.add_declare_transaction_impl(declare_transaction).await?) + } + + async fn add_deploy_account_transaction( + &self, + deploy_account_transaction: BroadcastedDeployAccountTx, + ) -> RpcResult { + Ok(self.add_deploy_account_transaction_impl(deploy_account_transaction).await?) + } +} diff --git a/crates/katana/rpc/rpc/src/torii.rs b/crates/katana/rpc/rpc/src/torii.rs index 60a957fe37..e972046fcb 100644 --- a/crates/katana/rpc/rpc/src/torii.rs +++ b/crates/katana/rpc/rpc/src/torii.rs @@ -6,6 +6,7 @@ use katana_core::sequencer::KatanaSequencer; use katana_core::service::block_producer::BlockProducerMode; use katana_executor::ExecutorFactory; use katana_primitives::block::{BlockHashOrNumber, FinalityStatus}; +use katana_provider::traits::block::BlockNumberProvider; use katana_provider::traits::transaction::TransactionProvider; use katana_rpc_api::torii::ToriiApiServer; use katana_rpc_types::error::torii::ToriiApiError; @@ -53,9 +54,9 @@ impl ToriiApiServer for ToriiApi { let mut transactions = Vec::new(); let mut next_cursor = cursor; - let provider = this.sequencer.backend.blockchain.provider(); + let provider = this.sequencer.backend().blockchain.provider(); let latest_block_number = - this.sequencer.block_number().map_err(ToriiApiError::from)?; + this.sequencer.provider().latest_number().map_err(ToriiApiError::from)?; if cursor.block_number > latest_block_number + 1 { return Err(ToriiApiError::BlockNotFound); diff --git a/crates/katana/rpc/rpc/tests/dev.rs b/crates/katana/rpc/rpc/tests/dev.rs new file mode 100644 index 0000000000..c2cb8c71d4 --- /dev/null +++ b/crates/katana/rpc/rpc/tests/dev.rs @@ -0,0 +1,142 @@ +use std::sync::Arc; + +use alloy_primitives::U256; +use katana_core::backend::config::StarknetConfig; +use katana_core::sequencer::{KatanaSequencer, SequencerConfig}; +use katana_executor::implementation::noop::NoopExecutorFactory; +use katana_primitives::genesis::allocation::DevAllocationsGenerator; +use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; +use katana_primitives::genesis::Genesis; +use katana_provider::traits::block::{BlockNumberProvider, BlockProvider}; +use katana_provider::traits::env::BlockEnvProvider; +use katana_rpc::dev::DevApi; + +fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { + let accounts = DevAllocationsGenerator::new(2) + .with_balance(U256::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE)) + .generate(); + + let mut genesis = Genesis::default(); + genesis.extend_allocations(accounts.into_iter().map(|(k, v)| (k, v.into()))); + + ( + SequencerConfig::default(), + StarknetConfig { genesis, disable_fee: true, ..Default::default() }, + ) +} + +async fn create_test_sequencer() -> Arc> { + let executor_factory = NoopExecutorFactory::new(); + let (sequencer_config, starknet_config) = create_test_sequencer_config(); + let sequencer = + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config).await.unwrap(); + Arc::new(sequencer) +} + +#[tokio::test] +async fn test_next_block_timestamp_in_past() { + let sequencer = create_test_sequencer().await; + let api = DevApi::new(Arc::clone(&sequencer)); + + let block_num = sequencer.provider().latest_number().unwrap(); + let mut block_env = sequencer.provider().block_env_at(block_num.into()).unwrap().unwrap(); + sequencer.backend().update_block_env(&mut block_env); + + let block1 = sequencer.backend().mine_empty_block(&block_env).unwrap().block_number; + let block1_timestamp = + sequencer.provider().block(block1.into()).unwrap().unwrap().header.timestamp; + api.set_next_block_timestamp(block1_timestamp - 1000).unwrap(); + + let block_num = sequencer.provider().latest_number().unwrap(); + let mut block_env = sequencer.provider().block_env_at(block_num.into()).unwrap().unwrap(); + sequencer.backend().update_block_env(&mut block_env); + + let block2 = sequencer.backend().mine_empty_block(&block_env).unwrap().block_number; + let block2_timestamp = + sequencer.provider().block(block2.into()).unwrap().unwrap().header.timestamp; + + assert_eq!(block2_timestamp, block1_timestamp - 1000, "timestamp should be updated"); +} + +#[tokio::test] +async fn test_set_next_block_timestamp_in_future() { + let sequencer = create_test_sequencer().await; + let api = DevApi::new(Arc::clone(&sequencer)); + + let block_num = sequencer.provider().latest_number().unwrap(); + let mut block_env = sequencer.provider().block_env_at(block_num.into()).unwrap().unwrap(); + sequencer.backend().update_block_env(&mut block_env); + let block1 = sequencer.backend().mine_empty_block(&block_env).unwrap().block_number; + + let block1_timestamp = + sequencer.provider().block(block1.into()).unwrap().unwrap().header.timestamp; + + api.set_next_block_timestamp(block1_timestamp + 1000).unwrap(); + + let block_num = sequencer.provider().latest_number().unwrap(); + let mut block_env = sequencer.provider().block_env_at(block_num.into()).unwrap().unwrap(); + sequencer.backend().update_block_env(&mut block_env); + let block2 = sequencer.backend().mine_empty_block(&block_env).unwrap().block_number; + + let block2_timestamp = + sequencer.provider().block(block2.into()).unwrap().unwrap().header.timestamp; + + assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); +} + +#[tokio::test] +async fn test_increase_next_block_timestamp() { + let sequencer = create_test_sequencer().await; + let api = DevApi::new(Arc::clone(&sequencer)); + + let block_num = sequencer.provider().latest_number().unwrap(); + let mut block_env = sequencer.provider().block_env_at(block_num.into()).unwrap().unwrap(); + sequencer.backend().update_block_env(&mut block_env); + let block1 = sequencer.backend().mine_empty_block(&block_env).unwrap().block_number; + + let block1_timestamp = + sequencer.provider().block(block1.into()).unwrap().unwrap().header.timestamp; + + api.increase_next_block_timestamp(1000).unwrap(); + + let block_num = sequencer.provider().latest_number().unwrap(); + let mut block_env = sequencer.provider().block_env_at(block_num.into()).unwrap().unwrap(); + sequencer.backend().update_block_env(&mut block_env); + let block2 = sequencer.backend().mine_empty_block(&block_env).unwrap().block_number; + + let block2_timestamp = + sequencer.provider().block(block2.into()).unwrap().unwrap().header.timestamp; + + // Depending on the current time and the machine we run on, we may have 1 sec difference + // between the expected and actual timestamp. + // We take this possible delay in account to have the test more robust for now, + // but it may due to how the timestamp is updated in the sequencer. + assert!( + block2_timestamp == block1_timestamp + 1000 || block2_timestamp == block1_timestamp + 1001, + "timestamp should be updated" + ); +} + +// #[tokio::test] +// async fn test_set_storage_at_on_instant_mode() { +// let sequencer = create_test_sequencer().await; +// sequencer.backend().mine_empty_block(); + +// let contract_address = ContractAddress(patricia_key!("0x1337")); +// let key = StorageKey(patricia_key!("0x20")); +// let val = stark_felt!("0xABC"); + +// { +// let mut state = sequencer.backend().state.write().await; +// let read_val = state.get_storage_at(contract_address, key).unwrap(); +// assert_eq!(stark_felt!("0x0"), read_val, "latest storage value should be 0"); +// } + +// sequencer.set_storage_at(contract_address, key, val).await.unwrap(); + +// { +// let mut state = sequencer.backend().state.write().await; +// let read_val = state.get_storage_at(contract_address, key).unwrap(); +// assert_eq!(val, read_val, "latest storage value incorrect after generate"); +// } +// }