From 810afd723b3098c81e249ec0b73a5034f37479c9 Mon Sep 17 00:00:00 2001 From: Kariy Date: Mon, 4 Dec 2023 19:16:08 +0800 Subject: [PATCH] wip --- crates/dojo-world/src/migration/mod.rs | 20 +- crates/dojo-world/src/utils.rs | 2 +- crates/katana/core/Cargo.toml | 1 + crates/katana/core/src/accounts.rs | 10 +- .../katana/core/src/backend/in_memory_db.rs | 358 ------------------ crates/katana/core/src/backend/mod.rs | 46 +-- crates/katana/core/src/backend/storage.rs | 74 ++-- crates/katana/core/src/sequencer.rs | 356 +++++++---------- crates/katana/core/src/sequencer_error.rs | 8 +- .../katana/core/src/service/block_producer.rs | 50 ++- .../core/src/service/messaging/ethereum.rs | 9 - .../core/src/service/messaging/starknet.rs | 288 +++++++------- crates/katana/core/src/utils/mod.rs | 2 +- crates/katana/core/tests/backend.rs | 23 +- crates/katana/core/tests/sequencer.rs | 230 +++-------- crates/katana/executor/src/blockifier/mod.rs | 7 +- .../katana/executor/src/blockifier/state.rs | 84 ++-- .../katana/executor/src/blockifier/utils.rs | 7 +- crates/katana/primitives/src/block.rs | 4 +- crates/katana/primitives/src/env.rs | 33 ++ crates/katana/primitives/src/lib.rs | 1 + crates/katana/primitives/src/receipt.rs | 6 - crates/katana/primitives/src/transaction.rs | 77 ++-- .../katana/primitives/src/utils/contract.rs | 1 - .../primitives/src/utils/transaction.rs | 5 +- .../katana/rpc/rpc-types-builder/Cargo.toml | 2 +- .../katana/rpc/rpc-types-builder/src/block.rs | 1 - .../rpc/rpc-types-builder/src/state_update.rs | 2 +- crates/katana/rpc/src/api/starknet.rs | 41 +- crates/katana/rpc/src/katana.rs | 8 +- crates/katana/rpc/src/starknet.rs | 169 +++++---- crates/katana/storage/provider/src/lib.rs | 4 +- .../provider/src/providers/fork/mod.rs | 20 +- .../provider/src/providers/in_memory/cache.rs | 2 +- .../provider/src/providers/in_memory/mod.rs | 30 +- .../storage/provider/src/traits/block.rs | 32 +- .../katana/storage/provider/src/traits/env.rs | 8 + .../katana/storage/provider/src/traits/mod.rs | 1 + .../storage/provider/src/traits/state.rs | 16 +- .../provider/src/traits/transaction.rs | 2 +- 40 files changed, 762 insertions(+), 1278 deletions(-) delete mode 100644 crates/katana/core/src/backend/in_memory_db.rs create mode 100644 crates/katana/primitives/src/env.rs delete mode 100644 crates/katana/primitives/src/utils/contract.rs create mode 100644 crates/katana/storage/provider/src/traits/env.rs diff --git a/crates/dojo-world/src/migration/mod.rs b/crates/dojo-world/src/migration/mod.rs index f58c2ba662..fa1c659c75 100644 --- a/crates/dojo-world/src/migration/mod.rs +++ b/crates/dojo-world/src/migration/mod.rs @@ -162,7 +162,7 @@ pub trait Deployable: Declarable + Sync { calldata: vec![], entry_point_selector: get_selector_from_name("base").unwrap(), }, - BlockId::Tag(BlockTag::Latest), + BlockId::Tag(BlockTag::Pending), ) .await .map_err(MigrationError::Provider)?; @@ -246,6 +246,8 @@ pub trait Deployable: Declarable + Sync { FieldElement::ZERO, ); + println!("ohayo"); + match account .provider() .get_class_hash_at(BlockId::Tag(BlockTag::Pending), contract_address) @@ -254,12 +256,22 @@ pub trait Deployable: Declarable + Sync { Err(ProviderError::StarknetError(StarknetErrorWithMessage { code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), .. - })) => {} + })) => { + println!("deploying contract so deploy") + } - Ok(_) => return Err(MigrationError::ContractAlreadyDeployed(contract_address)), - Err(e) => return Err(MigrationError::Provider(e)), + Ok(res) => { + println!("hmmm {res:#x}"); + return Err(MigrationError::ContractAlreadyDeployed(contract_address)); + } + Err(e) => { + println!("error {e}"); + return Err(MigrationError::Provider(e)); + } } + println!("ohayo 12"); + let mut txn = account.execute(vec![Call { calldata, // devnet UDC address diff --git a/crates/dojo-world/src/utils.rs b/crates/dojo-world/src/utils.rs index 641527e850..8b5400f944 100644 --- a/crates/dojo-world/src/utils.rs +++ b/crates/dojo-world/src/utils.rs @@ -84,7 +84,7 @@ where P: Provider + Send, { const DEFAULT_TIMEOUT: Duration = Duration::from_secs(300); - const DEFAULT_INTERVAL: Duration = Duration::from_millis(2500); + const DEFAULT_INTERVAL: Duration = Duration::from_millis(250); pub fn new(tx: FieldElement, provider: &'a P) -> Self { Self { diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index 25f13ca1ff..a9cd4095ac 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -40,5 +40,6 @@ assert_matches.workspace = true hex = "0.4.3" [features] +default = [ "starknet-messaging" ] messaging = [ "ethers", "sha3" ] starknet-messaging = [ ] diff --git a/crates/katana/core/src/accounts.rs b/crates/katana/core/src/accounts.rs index 3aff223df2..439afb85ff 100644 --- a/crates/katana/core/src/accounts.rs +++ b/crates/katana/core/src/accounts.rs @@ -64,12 +64,12 @@ impl Account { fn deploy(&self, state: &dyn StateWriter) -> Result<()> { let address: ContractAddress = self.address.into(); // set the class hash at the account address - state.set_class_hash_of_contract(address, self.class_hash.into())?; + state.set_class_hash_of_contract(address, self.class_hash)?; // set the public key in the account contract state.set_storage( address, get_storage_var_address("Account_public_key", &[]).unwrap(), - self.public_key.into(), + self.public_key, )?; // initialze account nonce state.set_nonce(address, 1u128.into())?; @@ -79,8 +79,8 @@ impl Account { fn fund(&self, state: &dyn StateWriter) -> Result<()> { state.set_storage( *FEE_TOKEN_ADDRESS, - get_storage_var_address("ERC20_balances", &[self.address.into()]).unwrap(), - self.balance.into(), + get_storage_var_address("ERC20_balances", &[self.address]).unwrap(), + self.balance, )?; Ok(()) } @@ -114,7 +114,7 @@ impl DevAccountGenerator { total, seed: [0u8; 32], balance: FieldElement::ZERO, - class_hash: (*OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH).into(), + class_hash: (*OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH), contract_class: Arc::new((*OZ_V0_ACCOUNT_CONTRACT).clone()), } } diff --git a/crates/katana/core/src/backend/in_memory_db.rs b/crates/katana/core/src/backend/in_memory_db.rs deleted file mode 100644 index e43085063f..0000000000 --- a/crates/katana/core/src/backend/in_memory_db.rs +++ /dev/null @@ -1,358 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use anyhow::Result; -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::cached_state::CommitmentStateDiff; -use blockifier::state::errors::StateError; -use blockifier::state::state_api::{State, StateReader, StateResult}; -use starknet::core::types::FlattenedSierraClass; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::patricia_key; -use starknet_api::state::StorageKey; - -use crate::constants::{ - ERC20_CONTRACT, ERC20_CONTRACT_CLASS_HASH, ERC20_DECIMALS_STORAGE_SLOT, - ERC20_NAME_STORAGE_SLOT, ERC20_SYMBOL_STORAGE_SLOT, FEE_TOKEN_ADDRESS, UDC_ADDRESS, - UDC_CLASS_HASH, UDC_CONTRACT, -}; -use crate::db::cached::{AsCachedDb, CachedDb, ClassRecord, MaybeAsCachedDb, StorageRecord}; -use crate::db::serde::state::{ - SerializableClassRecord, SerializableState, SerializableStorageRecord, -}; -use crate::db::{AsStateRefDb, Database, StateExt, StateExtRef, StateRefDb}; - -/// A in memory state database implementation with empty cache db. -#[derive(Clone, Debug)] -pub struct MemDb { - pub db: CachedDb<()>, -} - -impl MemDb { - pub fn new() -> Self { - Self { db: CachedDb::new(()) } - } -} - -impl Default for MemDb { - fn default() -> Self { - let mut state = Self::new(); - deploy_fee_contract(&mut state); - deploy_universal_deployer_contract(&mut state); - state - } -} - -impl State for MemDb { - fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { - let current_nonce = self.get_nonce_at(contract_address)?; - let current_nonce_as_u64 = usize::try_from(current_nonce.0)? as u64; - let next_nonce_val = 1_u64 + current_nonce_as_u64; - let next_nonce = Nonce(StarkFelt::from(next_nonce_val)); - self.db.storage.entry(contract_address).or_default().nonce = next_nonce; - Ok(()) - } - - fn set_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - value: StarkFelt, - ) { - self.db.storage.entry(contract_address).or_default().storage.insert(key, value); - } - - fn set_class_hash_at( - &mut self, - contract_address: ContractAddress, - class_hash: ClassHash, - ) -> StateResult<()> { - if contract_address == ContractAddress::default() { - return Err(StateError::OutOfRangeContractAddress); - } - self.db.contracts.insert(contract_address, class_hash); - Ok(()) - } - - fn set_compiled_class_hash( - &mut self, - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - ) -> StateResult<()> { - if !self.db.classes.contains_key(&class_hash) { - return Err(StateError::UndeclaredClassHash(class_hash)); - } - self.db.classes.entry(class_hash).and_modify(|r| r.compiled_hash = compiled_class_hash); - Ok(()) - } - - fn set_contract_class( - &mut self, - class_hash: &ClassHash, - contract_class: ContractClass, - ) -> StateResult<()> { - let compiled_hash = CompiledClassHash(class_hash.0); - self.db.classes.insert(*class_hash, ClassRecord { class: contract_class, compiled_hash }); - Ok(()) - } - - fn to_state_diff(&self) -> CommitmentStateDiff { - unreachable!("to_state_diff should not be called on MemDb") - } -} - -impl StateReader for MemDb { - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - let value = self - .db - .storage - .get(&contract_address) - .and_then(|r| r.storage.get(&key)) - .copied() - .unwrap_or_default(); - Ok(value) - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - let nonce = self.db.storage.get(&contract_address).map(|r| r.nonce).unwrap_or_default(); - Ok(nonce) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.db - .classes - .get(class_hash) - .map(|r| r.class.clone()) - .ok_or(StateError::UndeclaredClassHash(*class_hash)) - } - - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - Ok(self.db.contracts.get(&contract_address).cloned().unwrap_or_default()) - } - - fn get_compiled_class_hash( - &mut self, - class_hash: ClassHash, - ) -> StateResult { - self.db - .classes - .get(&class_hash) - .map(|r| r.compiled_hash) - .ok_or(StateError::UndeclaredClassHash(class_hash)) - } -} - -impl StateExtRef for MemDb { - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - if let ContractClass::V0(_) = self.get_compiled_contract_class(class_hash)? { - return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); - }; - - self.db - .sierra_classes - .get(class_hash) - .cloned() - .ok_or(StateError::StateReadError("Missing Sierra class".to_string())) - } -} - -impl StateExt for MemDb { - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()> { - // check the class hash must not be a legacy contract - if let ContractClass::V0(_) = self.get_compiled_contract_class(&class_hash)? { - return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); - }; - self.db.sierra_classes.insert(class_hash, sierra_class); - Ok(()) - } -} - -impl AsStateRefDb for MemDb { - fn as_ref_db(&self) -> StateRefDb { - StateRefDb::new(MemDb { db: self.db.clone() }) - } -} - -impl MaybeAsCachedDb for MemDb { - fn maybe_as_cached_db(&self) -> Option { - Some(CachedDb { - db: (), - classes: self.db.classes.clone(), - storage: self.db.storage.clone(), - contracts: self.db.contracts.clone(), - sierra_classes: self.db.sierra_classes.clone(), - }) - } -} - -impl Database for MemDb { - fn dump_state(&self) -> Result { - let mut serializable = SerializableState::default(); - - self.db.storage.iter().for_each(|(addr, storage)| { - let mut record = SerializableStorageRecord { - storage: BTreeMap::new(), - nonce: storage.nonce.0.into(), - }; - - storage.storage.iter().for_each(|(key, value)| { - record.storage.insert((*key.0.key()).into(), (*value).into()); - }); - - serializable.storage.insert((*addr.0.key()).into(), record); - }); - - self.db.classes.iter().for_each(|(class_hash, class_record)| { - serializable.classes.insert( - class_hash.0.into(), - SerializableClassRecord { - class: class_record.class.clone().into(), - compiled_hash: class_record.compiled_hash.0.into(), - }, - ); - }); - - self.db.contracts.iter().for_each(|(address, class_hash)| { - serializable.contracts.insert((*address.0.key()).into(), class_hash.0.into()); - }); - - self.db.sierra_classes.iter().for_each(|(class_hash, class)| { - serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); - }); - - Ok(serializable) - } - - fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { - self.db.storage.entry(addr).or_default().nonce = nonce; - } -} - -fn deploy_fee_contract(state: &mut MemDb) { - let address = ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)); - let hash = ClassHash(*ERC20_CONTRACT_CLASS_HASH); - let compiled_hash = CompiledClassHash(*ERC20_CONTRACT_CLASS_HASH); - - state.db.classes.insert(hash, ClassRecord { class: (*ERC20_CONTRACT).clone(), compiled_hash }); - state.db.contracts.insert(address, hash); - state.db.storage.insert( - address, - StorageRecord { - nonce: Nonce(1_u128.into()), - storage: [ - (*ERC20_NAME_STORAGE_SLOT, 0x4574686572_u128.into()), - (*ERC20_SYMBOL_STORAGE_SLOT, 0x455448_u128.into()), - (*ERC20_DECIMALS_STORAGE_SLOT, 18_u128.into()), - ] - .into_iter() - .collect(), - }, - ); -} - -fn deploy_universal_deployer_contract(state: &mut MemDb) { - let address = ContractAddress(patricia_key!(*UDC_ADDRESS)); - let hash = ClassHash(*UDC_CLASS_HASH); - let compiled_hash = CompiledClassHash(*UDC_CLASS_HASH); - - state.db.classes.insert(hash, ClassRecord { class: (*UDC_CONTRACT).clone(), compiled_hash }); - state.db.contracts.insert(address, hash); - state - .db - .storage - .insert(address, StorageRecord { nonce: Nonce(1_u128.into()), storage: HashMap::new() }); -} - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use starknet_api::core::{ClassHash, PatriciaKey}; - use starknet_api::stark_felt; - - use super::*; - use crate::backend::in_memory_db::MemDb; - use crate::constants::UDC_CONTRACT; - // use crate::db::cached::CachedStateWrapper; - use crate::execution::ExecutionOutcome; - - #[test] - fn dump_and_load_state() { - let mut state = MemDb::new(); - - let class_hash = ClassHash(stark_felt!("0x1")); - let address = ContractAddress(patricia_key!("0x1")); - let storage_key = StorageKey(patricia_key!("0x77")); - let storage_val = stark_felt!("0x66"); - let contract = (*UDC_CONTRACT).clone(); - let compiled_hash = CompiledClassHash(class_hash.0); - - state.set_contract_class(&class_hash, (*UDC_CONTRACT).clone()).unwrap(); - state.set_compiled_class_hash(class_hash, CompiledClassHash(class_hash.0)).unwrap(); - state.set_class_hash_at(address, class_hash).unwrap(); - state.set_storage_at(address, storage_key, storage_val); - - let dump = state.dump_state().expect("should dump state"); - - let mut new_state = MemDb::new(); - new_state.load_state(dump).expect("should load state"); - - assert_eq!(new_state.get_compiled_contract_class(&class_hash).unwrap(), contract); - assert_eq!(new_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); - assert_eq!(new_state.get_class_hash_at(address).unwrap(), class_hash); - assert_eq!(new_state.get_storage_at(address, storage_key).unwrap(), storage_val); - } - - #[test] - fn apply_state_update() { - let mut old_state = MemDb::new(); - - let class_hash = ClassHash(stark_felt!("0x1")); - let address = ContractAddress(patricia_key!("0x1")); - let storage_key = StorageKey(patricia_key!("0x77")); - let storage_val = stark_felt!("0x66"); - let contract = (*UDC_CONTRACT).clone(); - let compiled_hash = CompiledClassHash(class_hash.0); - - let execution_outcome = ExecutionOutcome { - state_diff: CommitmentStateDiff { - address_to_class_hash: [(address, class_hash)].into(), - address_to_nonce: [].into(), - storage_updates: [(address, [(storage_key, storage_val)].into())].into(), - class_hash_to_compiled_class_hash: [(class_hash, CompiledClassHash(class_hash.0))] - .into(), - }, - transactions: vec![], - declared_classes: HashMap::from([(class_hash, (*UDC_CONTRACT).clone())]), - declared_sierra_classes: HashMap::new(), - }; - - assert_matches!( - old_state.get_compiled_contract_class(&class_hash), - Err(StateError::UndeclaredClassHash(_)) - ); - assert_matches!( - old_state.get_compiled_class_hash(class_hash), - Err(StateError::UndeclaredClassHash(_)) - ); - assert_eq!(old_state.get_class_hash_at(address).unwrap(), ClassHash::default()); - assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), StarkFelt::default()); - - execution_outcome.apply_to(&mut old_state); - - assert_eq!(old_state.get_compiled_contract_class(&class_hash).unwrap(), contract); - assert_eq!(old_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); - assert_eq!(old_state.get_class_hash_at(address).unwrap(), class_hash); - assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), storage_val); - } -} diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index fa7170594a..9bee50a480 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -2,8 +2,7 @@ use std::sync::Arc; use blockifier::block_context::BlockContext; use katana_primitives::block::{ - Block, BlockHash, BlockHashOrNumber, FinalityStatus, Header, PartialHeader, - SealedBlockWithStatus, + Block, BlockHashOrNumber, FinalityStatus, Header, PartialHeader, SealedBlockWithStatus, }; use katana_primitives::contract::ContractAddress; use katana_primitives::receipt::Receipt; @@ -33,7 +32,7 @@ use crate::accounts::{Account, DevAccountGenerator}; use crate::constants::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; use crate::env::{BlockContextGenerator, Env}; use crate::service::block_producer::MinedBlockOutcome; -use crate::utils::{get_current_timestamp, get_genesis_states_for_testing}; +use crate::utils::get_current_timestamp; pub struct Backend { /// The config used to generate the backend. @@ -54,14 +53,13 @@ impl Backend { let accounts = DevAccountGenerator::new(config.total_accounts) .with_seed(config.seed) - .with_balance((*DEFAULT_PREFUNDED_ACCOUNT_BALANCE).into()) + .with_balance(*DEFAULT_PREFUNDED_ACCOUNT_BALANCE) .generate(); let provider: Box = if let Some(forked_url) = &config.fork_rpc_url { let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(forked_url.clone()))); - let forked_chain_id = provider.chain_id().await.unwrap(); let forked_block_num = if let Some(num) = config.fork_block_number { @@ -81,8 +79,7 @@ impl Backend { block_context.block_number = BlockNumber(block.block_number); block_context.block_timestamp = BlockTimestamp(block.timestamp); - block_context.sequencer_address = - ContractAddress(block.sequencer_address.into()).into(); + block_context.sequencer_address = ContractAddress(block.sequencer_address).into(); block_context.chain_id = ChainId(parse_cairo_short_string(&forked_chain_id).unwrap()); trace!( @@ -98,8 +95,7 @@ impl Backend { Box::new(InMemoryProvider::new()) }; - let blockchain = Blockchain::new_with_state(provider, get_genesis_states_for_testing()); - + let blockchain = Blockchain::new_with_genesis(provider, &block_context).unwrap(); let env = Env { block: block_context }; for acc in &accounts { @@ -131,14 +127,6 @@ impl Backend { (outcome, new_state) } - /// Updates the block context and mines an empty block. - pub fn mine_empty_block(&self) -> MinedBlockOutcome { - let block_context = self.env.read().block.clone(); - let res = self.do_mine_block(block_context, Default::default(), Default::default()); - self.update_block_context(); - res - } - pub fn do_mine_block( &self, block_context: BlockContext, @@ -149,7 +137,6 @@ impl Backend { let prev_hash = BlockHashProvider::latest_hash(self.blockchain.provider()).unwrap(); - // create sealed block let partial_header = PartialHeader { parent_hash: prev_hash, gas_price: block_context.gas_price, @@ -177,21 +164,6 @@ impl Backend { MinedBlockOutcome { block_number } } - pub fn create_empty_block(&self, parent_hash: BlockHash, block_env: BlockContext) -> Block { - // let parent_hash = self.blockchain.provider().latest_hash().unwrap(); - // let block_context = &self.env.read().block; - - let partial_header = katana_primitives::block::PartialHeader { - parent_hash, - gas_price: block_env.gas_price, - number: block_env.block_number.0, - timestamp: block_env.block_timestamp.0, - sequencer_address: block_env.sequencer_address.into(), - }; - - Block { header: Header::new(partial_header, FieldElement::ZERO), body: vec![] } - } - pub fn update_block_context(&self) { let mut context_gen = self.block_context_generator.write(); let block_context = &mut self.env.write().block; @@ -209,4 +181,12 @@ impl Backend { block_context.block_number = block_context.block_number.next(); block_context.block_timestamp = BlockTimestamp(timestamp); } + + /// Updates the block context and mines an empty block. + pub fn mine_empty_block(&self) -> MinedBlockOutcome { + self.update_block_context(); + let block_context = self.env.read().block.clone(); + let res = self.do_mine_block(block_context, Default::default(), Default::default()); + res + } } diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index d0c659a3f2..f6b32b1c6e 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -1,12 +1,11 @@ -use std::collections::{HashMap, VecDeque}; -use std::sync::Arc; - -use katana_primitives::contract::{ClassHash, CompiledClassHash, CompiledContractClass}; +use anyhow::Result; +use blockifier::block_context::BlockContext; +use katana_primitives::block::{ + Block, FinalityStatus, Header, PartialHeader, SealedBlock, SealedBlockWithStatus, +}; use katana_primitives::state::StateUpdatesWithDeclaredClasses; use katana_provider::traits::block::{BlockProvider, BlockWriter}; -use katana_provider::traits::contract::{ - ContractClassProvider, ContractClassWriter, ContractInfoProvider, -}; +use katana_provider::traits::contract::{ContractClassWriter, ContractInfoProvider}; use katana_provider::traits::state::{StateFactoryProvider, StateRootProvider, StateWriter}; use katana_provider::traits::state_update::StateUpdateProvider; use katana_provider::traits::transaction::{ @@ -14,10 +13,8 @@ use katana_provider::traits::transaction::{ }; use katana_provider::BlockchainProvider; -use crate::constants::{ - ERC20_CONTRACT, ERC20_CONTRACT_CLASS_HASH, FEE_TOKEN_ADDRESS, OZ_V0_ACCOUNT_CONTRACT, - OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH, UDC_ADDRESS, UDC_CLASS_HASH, UDC_CONTRACT, -}; +use crate::constants::SEQUENCER_ADDRESS; +use crate::utils::get_genesis_states_for_testing; pub trait Database: BlockProvider @@ -70,43 +67,26 @@ impl Blockchain { &self.inner } - pub fn new_with_state( + pub fn new_with_genesis(provider: impl Database, block_context: &BlockContext) -> Result { + let header = PartialHeader { + gas_price: block_context.gas_price, + number: 0, + parent_hash: 0u8.into(), + timestamp: block_context.block_timestamp.0, + sequencer_address: *SEQUENCER_ADDRESS, + }; + + let block = Block { header: Header::new(header, 0u8.into()), body: vec![] }.seal(); + Self::new_with_block_and_state(provider, block, get_genesis_states_for_testing()) + } + + fn new_with_block_and_state( provider: impl Database, + block: SealedBlock, states: StateUpdatesWithDeclaredClasses, - ) -> Self { - // set classses - for (class_hash, compiled_class_hash) in states.state_updates.declared_classes { - provider - .set_compiled_class_hash_of_class_hash(class_hash, compiled_class_hash) - .unwrap(); - } - - for (class_hash, class) in states.declared_compiled_classes { - provider.set_class(class_hash, class).unwrap(); - } - - for (class_hash, sierra_class) in states.declared_sierra_classes { - provider.set_sierra_class(class_hash, sierra_class).unwrap(); - } - - // set contracts - - for (address, class_hash) in states.state_updates.contract_updates { - provider.set_class_hash_of_contract(address, class_hash).unwrap(); - } - - // storages and nonces - - for (address, nonce) in states.state_updates.nonce_updates { - provider.set_nonce(address, nonce).unwrap(); - } - - for (address, storage_changes) in states.state_updates.storage_updates { - for (key, value) in storage_changes { - provider.set_storage(address, key, value).unwrap(); - } - } - - Self::new(provider) + ) -> Result { + let block = SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL1 }; + BlockWriter::insert_block_with_states_and_receipts(&provider, block, states, vec![])?; + Ok(Self::new(provider)) } } diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index ab463fb851..bf04269eea 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -1,28 +1,28 @@ +use std::cmp::Ordering; use std::iter::Skip; use std::slice::Iter; use std::sync::Arc; use anyhow::Result; +use blockifier::execution::errors::{EntryPointExecutionError, PreExecutionError}; use katana_executor::blockifier::state::StateRefDb; use katana_executor::blockifier::utils::EntryPointCall; use katana_executor::blockifier::PendingState; -use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockId, BlockNumber, PartialHeader}; +use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockIdOrTag, BlockNumber}; use katana_primitives::contract::{ ClassHash, CompiledContractClass, ContractAddress, Nonce, StorageKey, StorageValue, }; -use katana_primitives::receipt::MaybePendingReceipt; -use katana_primitives::state::StateUpdates; -use katana_primitives::transaction::{ - DeclareTx, DeployAccountTx, ExecutableTx, ExecutableTxWithHash, InvokeTx, Tx, TxHash, - TxWithHash, -}; +use katana_primitives::transaction::{ExecutableTxWithHash, TxHash, TxWithHash}; use katana_primitives::FieldElement; -use katana_provider::traits::block::{BlockHashProvider, BlockNumberProvider, BlockProvider}; +use katana_provider::traits::block::{ + BlockHashProvider, BlockIdReader, BlockNumberProvider, BlockProvider, +}; use katana_provider::traits::contract::ContractClassProvider; use katana_provider::traits::state::{StateFactoryProvider, StateProvider}; -use katana_provider::traits::state_update::StateUpdateProvider; -use katana_provider::traits::transaction::{ReceiptProvider, TransactionProvider}; -use starknet::core::types::{BlockTag, Event, EventsPage, FeeEstimate}; +use katana_provider::traits::transaction::{ + ReceiptProvider, TransactionProvider, TransactionsProviderExt, +}; +use starknet::core::types::{BlockTag, EmittedEvent, Event, EventsPage, FeeEstimate}; use starknet_api::core::ChainId; use crate::backend::config::StarknetConfig; @@ -99,17 +99,6 @@ impl KatanaSequencer { } } - fn verify_contract_exists( - &self, - block_id: &BlockId, - contract_address: &ContractAddress, - ) -> bool { - let state = self.state(block_id).unwrap(); - StateProvider::class_hash_of_contract(&state, *contract_address) - .unwrap() - .is_some_and(|c| c != ClassHash::default()) - } - pub fn block_producer(&self) -> &BlockProducer { &self.block_producer } @@ -118,16 +107,16 @@ impl KatanaSequencer { &self.backend } - pub fn state(&self, block_id: &BlockId) -> SequencerResult> { + pub fn state(&self, block_id: &BlockIdOrTag) -> SequencerResult> { let provider = self.backend.blockchain.provider(); match block_id { - BlockId::Tag(BlockTag::Latest) => { + BlockIdOrTag::Tag(BlockTag::Latest) => { let state = StateFactoryProvider::latest(provider)?; Ok(state) } - BlockId::Tag(BlockTag::Pending) => { + BlockIdOrTag::Tag(BlockTag::Pending) => { if let Some(state) = self.pending_state() { Ok(Box::new(state.state.clone())) } else { @@ -136,12 +125,12 @@ impl KatanaSequencer { } } - BlockId::Hash(hash) => { + BlockIdOrTag::Hash(hash) => { StateFactoryProvider::historical(provider, BlockHashOrNumber::Hash(*hash))? .ok_or(SequencerError::BlockNotFound(*block_id)) } - BlockId::Number(num) => { + BlockIdOrTag::Number(num) => { StateFactoryProvider::historical(provider, BlockHashOrNumber::Num(*num))? .ok_or(SequencerError::BlockNotFound(*block_id)) } @@ -155,7 +144,7 @@ impl KatanaSequencer { pub fn estimate_fee( &self, transactions: Vec, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> SequencerResult> { let state = self.state(&block_id)?; let block_context = self.backend.env.read().block.clone(); @@ -176,7 +165,7 @@ impl KatanaSequencer { pub fn class_hash_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: ContractAddress, ) -> SequencerResult> { let state = self.state(&block_id)?; @@ -186,7 +175,7 @@ impl KatanaSequencer { pub fn class( &self, - block_id: BlockId, + block_id: BlockIdOrTag, class_hash: ClassHash, ) -> SequencerResult> { let state = self.state(&block_id)?; @@ -209,7 +198,7 @@ impl KatanaSequencer { &self, contract_address: ContractAddress, storage_key: StorageKey, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> SequencerResult { let state = self.state(&block_id)?; let Some(value) = StateProvider::storage(&state, contract_address, storage_key)? else { @@ -226,11 +215,11 @@ impl KatanaSequencer { BlockNumberProvider::latest_number(&self.backend.blockchain.provider()).unwrap() } - pub fn block_tx_count(&self, block_id: BlockId) -> SequencerResult> { + pub fn block_tx_count(&self, block_id: BlockIdOrTag) -> SequencerResult> { let provider = self.backend.blockchain.provider(); let count = match block_id { - BlockId::Tag(BlockTag::Pending) => match self.pending_state() { + BlockIdOrTag::Tag(BlockTag::Pending) => match self.pending_state() { Some(state) => Some(state.executed_txs.read().len() as u64), None => { let hash = BlockHashProvider::latest_hash(provider)?; @@ -238,16 +227,16 @@ impl KatanaSequencer { } }, - BlockId::Tag(BlockTag::Latest) => { + BlockIdOrTag::Tag(BlockTag::Latest) => { let num = BlockNumberProvider::latest_number(provider)?; TransactionProvider::transaction_count_by_block(provider, num.into())? } - BlockId::Number(num) => { + BlockIdOrTag::Number(num) => { TransactionProvider::transaction_count_by_block(provider, num.into())? } - BlockId::Hash(hash) => { + BlockIdOrTag::Hash(hash) => { TransactionProvider::transaction_count_by_block(provider, hash.into())? } }; @@ -257,7 +246,7 @@ impl KatanaSequencer { pub async fn nonce_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: ContractAddress, ) -> SequencerResult> { let state = self.state(&block_id)?; @@ -268,17 +257,19 @@ impl KatanaSequencer { pub fn call( &self, request: EntryPointCall, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> SequencerResult> { - if !self.verify_contract_exists(&block_id, &request.contract_address) { - return Err(SequencerError::ContractNotFound(request.contract_address)); - } - let state = self.state(&block_id)?; let block_context = self.backend.env.read().block.clone(); let retdata = katana_executor::blockifier::utils::call(request, block_context, state) - .map_err(SequencerError::EntryPointExecution)?; + .map_err(|e| match e { + EntryPointExecutionError::PreExecutionError( + PreExecutionError::UninitializedStorageAddress(addr), + ) => SequencerError::ContractNotFound(addr.into()), + + _ => SequencerError::EntryPointExecution(e), + })?; Ok(retdata) } @@ -302,182 +293,136 @@ impl KatanaSequencer { pub async fn events( &self, - from_block: BlockId, - to_block: BlockId, + 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 = BlockNumberProvider::block_number_by_hash(provider, hash) - - // let to = storage - // .block_hash(to_block) - // .and_then(|hash| storage.storage.read().blocks.get(&hash).map(|b| - // b.header.number)) .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 = self - // .backend - // .blockchain - // .storage - // .read() - // .block_by_number(i) - // .cloned() - // .ok_or(SequencerError::BlockNotFound(BlockId::Number(i)))?; - - // // to get the current block hash we need to get the parent hash of the next block - // // if the current block is the latest block then we use the latest hash - // let block_hash = self - // .backend - // .blockchain - // .storage - // .read() - // .block_by_number(i + 1) - // .map(|b| b.header.parent_hash) - // .unwrap_or(self.backend.blockchain.storage.read().latest_hash); - - // let block_number = i; - - // let txn_n = block.transactions.len(); - // if (txn_n as u64) < continuation_token.txn_n { - // return Err(SequencerError::ContinuationToken( - // ContinuationTokenError::InvalidToken, - // )); - // } - - // for (txn_output, txn) in - // block.outputs.iter().zip(block.transactions).skip(continuation_token.txn_n as - // usize) { - // let txn_events_len: usize = txn_output.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 = txn_output.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, - // keys: e.keys.clone(), - // data: e.data.clone(), - // block_hash, - // block_number, - // transaction_hash: txn.inner.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 }) - - todo!() - } + 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) + }; - // pub async fn state_update(&self, block_id: BlockId) -> SequencerResult { - // match block_id { - // BlockId::Tag(BlockTag::Pending) => Err(SequencerError::BlockNotFound(block_id)), - - // BlockId::Tag(BlockTag::Latest) => { - // let hash = BlockHashProvider::latest_hash(self.backend.blockchain.provider())?; - // StateUpdateProvider::state_update( - // self.backend.blockchain.provider(), - // BlockHashOrNumber::Hash(hash), - // )? - // .ok_or(SequencerError::BlockNotFound(block_id)) - // } - - // BlockId::Hash(hash) => StateUpdateProvider::state_update( - // self.backend.blockchain.provider(), - // BlockHashOrNumber::Hash(hash), - // )? - // .ok_or(SequencerError::BlockNotFound(block_id)), - - // BlockId::Number(num) => StateUpdateProvider::state_update( - // self.backend.blockchain.provider(), - // BlockHashOrNumber::Num(num), - // )? - // .ok_or(SequencerError::BlockNotFound(block_id)), - // } - // } + 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); - pub async fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { - if self.has_pending_transactions().await { + 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, + keys: e.keys.clone(), + data: e.data.clone(), + block_hash, + block_number: 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 async fn increase_next_block_timestamp( - &self, - timestamp: u64, - ) -> Result<(), SequencerError> { - if self.has_pending_transactions().await { + 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 async fn has_pending_transactions(&self) -> bool { + pub fn has_pending_transactions(&self) -> bool { if let Some(ref pending) = self.pending_state() { !pending.executed_txs.read().is_empty() } else { @@ -492,17 +437,8 @@ impl KatanaSequencer { // value: StorageValue, // ) -> Result<(), SequencerError> { // if let Some(ref pending) = self.pending_state() { - // pending.state.inner_mut().set_storage_at( - // contract_address.into(), - // starknet_api::state::StorageKey(patricia_key!(storage_key)), - // value.into(), - // ); - // } else { - // let state = - // StateFactoryProvider::latest(self.backend.blockchain.provider()).unwrap(); - // StateWriter::set_storage(&state, contract_address, storage_key, value).unwrap(); + // StateWriter::set_storage(&pending.state, contract_address, storage_key, value)?; // } - // Ok(()) // } } diff --git a/crates/katana/core/src/sequencer_error.rs b/crates/katana/core/src/sequencer_error.rs index 6377330379..6b58f0540d 100644 --- a/crates/katana/core/src/sequencer_error.rs +++ b/crates/katana/core/src/sequencer_error.rs @@ -1,7 +1,7 @@ use blockifier::execution::errors::EntryPointExecutionError; use blockifier::state::errors::StateError; use blockifier::transaction::errors::TransactionExecutionError; -use katana_primitives::block::BlockId; +use katana_primitives::block::BlockIdOrTag; use katana_primitives::contract::ContractAddress; use katana_primitives::transaction::TxHash; use starknet_api::StarknetApiError; @@ -11,13 +11,13 @@ use crate::utils::event::ContinuationTokenError; #[derive(Debug, thiserror::Error)] pub enum SequencerError { #[error("Block {0:?} not found.")] - BlockNotFound(BlockId), + BlockNotFound(BlockIdOrTag), #[error("Contract address {0:?} not found.")] ContractNotFound(ContractAddress), #[error("State update for block {0:?} not found.")] - StateUpdateNotFound(BlockId), + StateUpdateNotFound(BlockIdOrTag), #[error("State for block {0:?} not found.")] - StateNotFound(BlockId), + StateNotFound(BlockIdOrTag), #[error("Transaction with {0} hash not found.")] TxnNotFound(TxHash), #[error(transparent)] diff --git a/crates/katana/core/src/service/block_producer.rs b/crates/katana/core/src/service/block_producer.rs index 737442e890..f283b9d68d 100644 --- a/crates/katana/core/src/service/block_producer.rs +++ b/crates/katana/core/src/service/block_producer.rs @@ -93,12 +93,8 @@ impl BlockProducer { trace!(target: "miner", "force mining"); let mut mode = self.inner.write(); match &mut *mode { - BlockProducerMode::Instant(producer) => { - tokio::task::block_in_place(|| futures::executor::block_on(producer.force_mine())) - } - BlockProducerMode::Interval(producer) => { - tokio::task::block_in_place(|| futures::executor::block_on(producer.force_mine())) - } + BlockProducerMode::Instant(producer) => producer.force_mine(), + BlockProducerMode::Interval(producer) => producer.force_mine(), } } } @@ -198,16 +194,16 @@ impl IntervalBlockProducer { } /// Force mine a new block. It will only able to mine if there is no ongoing mining process. - pub async fn force_mine(&self) { + pub fn force_mine(&self) { if self.block_mining.is_none() { let outcome = self.outcome(); - let _ = Self::do_mine(outcome, self.backend.clone(), self.state.clone()).await; + let _ = Self::do_mine(outcome, self.backend.clone(), self.state.clone()); } else { trace!(target: "miner", "unable to force mine while a mining process is running") } } - async fn do_mine( + fn do_mine( state_updates: StateUpdatesWithDeclaredClasses, backend: Arc, pending_state: Arc, @@ -216,13 +212,7 @@ impl IntervalBlockProducer { let tx_receipt_pairs = { let mut txs_lock = pending_state.executed_txs.write(); - // let mut tx_res_lock = pending_state.executed_tx_results.write(); - txs_lock.drain(..).map(|(tx, rct)| (tx, rct.receipt)).collect::>() - // let receipts = tx_res_lock.drain(..).map(|res| res.receipt); - - // tx.collect::>() - // tx.zip(receipts).collect::>() }; let (outcome, new_state) = backend.mine_pending_block(tx_receipt_pairs, state_updates); @@ -277,11 +267,15 @@ impl Stream for IntervalBlockProducer { if let Some(interval) = &mut pin.interval { if interval.poll_tick(cx).is_ready() && pin.block_mining.is_none() { - pin.block_mining = Some(Box::pin(Self::do_mine( - pin.outcome(), - pin.backend.clone(), - pin.state.clone(), - ))); + let backend = pin.backend.clone(); + let outcome = pin.outcome(); + let state = pin.state.clone(); + + pin.block_mining = Some(Box::pin(async move { + tokio::task::spawn_blocking(|| Self::do_mine(outcome, backend, state)) + .await + .unwrap() + })); } } @@ -319,16 +313,16 @@ impl InstantBlockProducer { Self { backend, block_mining: None, queued: VecDeque::default() } } - pub async fn force_mine(&mut self) { + pub fn force_mine(&mut self) { if self.block_mining.is_none() { let txs = self.queued.pop_front().unwrap_or_default(); - let _ = Self::do_mine(self.backend.clone(), txs).await; + let _ = Self::do_mine(self.backend.clone(), txs); } else { trace!(target: "miner", "unable to force mine while a mining process is running") } } - async fn do_mine( + fn do_mine( backend: Arc, transactions: Vec, ) -> MinedBlockOutcome { @@ -338,13 +332,13 @@ impl InstantBlockProducer { let latest_state = StateFactoryProvider::latest(backend.blockchain.provider()) .expect("able to get latest state"); - let mut state = CachedStateWrapper::new(latest_state.into()); + let state = CachedStateWrapper::new(latest_state.into()); let block_context = backend.env.read().block.clone(); let txs = transactions.iter().map(TxWithHash::from); let tx_receipt_pairs: Vec<(TxWithHash, Receipt)> = TransactionExecutor::new( - &mut state, + &state, &block_context, !backend.config.read().disable_fee, transactions.clone().into_iter(), @@ -384,7 +378,11 @@ impl Stream for InstantBlockProducer { if !pin.queued.is_empty() && pin.block_mining.is_none() { let transactions = pin.queued.pop_front().expect("not empty; qed"); - pin.block_mining = Some(Box::pin(Self::do_mine(pin.backend.clone(), transactions))); + let backend = pin.backend.clone(); + + pin.block_mining = Some(Box::pin(async move { + tokio::task::spawn_blocking(|| Self::do_mine(backend, transactions)).await.unwrap() + })); } // poll the mining future diff --git a/crates/katana/core/src/service/messaging/ethereum.rs b/crates/katana/core/src/service/messaging/ethereum.rs index fe8f22bfcc..de85e2b283 100644 --- a/crates/katana/core/src/service/messaging/ethereum.rs +++ b/crates/katana/core/src/service/messaging/ethereum.rs @@ -218,15 +218,6 @@ fn l1_handler_tx_from_log(log: Log, chain_id: FieldElement) -> MessengerResult = vec![]; + let mut l1_handler_txs: Vec = vec![]; self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) .await @@ -308,14 +301,11 @@ fn parse_messages(messages: &[MsgToL1]) -> MessengerResult<(Vec, V Ok((hashes, calls)) } -fn l1_handler_tx_from_event( - event: &EmittedEvent, - chain_id: FieldElement, -) -> Result { +fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: FieldElement) -> Result { if event.keys[0] != selector!("MessageSentToAppchain") { debug!( target: LOG_TARGET, - "Event with key {:?} can't be converted into L1HandlerTransaction", event.keys[0], + "Event with key {:?} can't be converted into L1HandlerTx", event.keys[0], ); return Err(Error::GatherError.into()); } @@ -327,7 +317,7 @@ fn l1_handler_tx_from_event( // See contrat appchain_messaging.cairo for MessageSentToAppchain event. let from_address = event.keys[2]; let to_address = event.keys[3]; - let selector = event.data[0]; + let entry_point_selector = event.data[0]; let nonce = event.data[1]; let version = 0_u32; @@ -336,35 +326,17 @@ fn l1_handler_tx_from_event( let mut calldata = vec![from_address]; calldata.extend(&event.data[3..]); - let tx_hash = compute_l1_handler_transaction_hash_felts( - version.into(), - to_address, - selector, - &calldata, - chain_id, + Ok(L1HandlerTx { nonce, - ); - - let calldata: Vec = calldata.iter().map(|f| StarkFelt::from(*f)).collect(); - let calldata = Calldata(calldata.into()); - - let tx = L1HandlerTransaction { - inner: ApiL1HandlerTransaction { - transaction_hash: TransactionHash(tx_hash.into()), - version: TransactionVersion(stark_felt!(version)), - nonce: Nonce(nonce.into()), - contract_address: ContractAddress::try_from(>::into( - to_address, - )) - .unwrap(), - entry_point_selector: EntryPointSelector(selector.into()), - calldata, - }, + calldata, + chain_id, // This is the min value paid on L1 for the message to be sent to L2. - paid_l1_fee: 30000_u128, - }; - - Ok(tx) + paid_fee_on_l1: 30000_u128, + entry_point_selector, + // transaction_hash: hash, + version: FieldElement::ZERO, + contract_address: to_address.into(), + }) } #[cfg(test)] @@ -430,119 +402,119 @@ mod tests { parse_messages(&messages).unwrap(); } - #[test] - fn l1_handler_tx_from_event_parse_ok() { - let from_address = selector!("from_address"); - let to_address = selector!("to_address"); - let selector = selector!("selector"); - let chain_id = selector!("KATANA"); - let nonce = FieldElement::ONE; - let calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; - let transaction_hash: FieldElement = compute_l1_handler_transaction_hash_felts( - FieldElement::ZERO, - to_address, - selector, - &stark_felt_to_field_element_array(&calldata), - chain_id, - nonce, - ); - - let event = EmittedEvent { - from_address: felt!( - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" - ), - keys: vec![ - selector!("MessageSentToAppchain"), - selector!("random_hash"), - from_address, - to_address, - ], - data: vec![ - selector, - nonce, - FieldElement::from(calldata.len() as u128), - FieldElement::THREE, - ], - block_hash: selector!("block_hash"), - block_number: 0, - transaction_hash, - }; - - let expected = L1HandlerTransaction { - inner: ApiL1HandlerTransaction { - transaction_hash: TransactionHash(transaction_hash.into()), - version: TransactionVersion(stark_felt!(0_u32)), - nonce: Nonce(nonce.into()), - contract_address: ContractAddress::try_from( - >::into(to_address), - ) - .unwrap(), - entry_point_selector: EntryPointSelector(selector.into()), - calldata: Calldata(calldata.into()), - }, - paid_l1_fee: 30000_u128, - }; - - let tx = l1_handler_tx_from_event(&event, chain_id).unwrap(); - - assert_eq!(tx.inner, expected.inner); - } - - #[test] - #[should_panic] - fn l1_handler_tx_from_event_parse_bad_selector() { - let from_address = selector!("from_address"); - let to_address = selector!("to_address"); - let selector = selector!("selector"); - let nonce = FieldElement::ONE; - let calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; - let transaction_hash = FieldElement::ZERO; - - let event = EmittedEvent { - from_address: felt!( - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" - ), - keys: vec![ - selector!("AnOtherUnexpectedEvent"), - selector!("random_hash"), - from_address, - to_address, - ], - data: vec![ - selector, - nonce, - FieldElement::from(calldata.len() as u128), - FieldElement::THREE, - ], - block_hash: selector!("block_hash"), - block_number: 0, - transaction_hash, - }; - - let _tx = l1_handler_tx_from_event(&event, FieldElement::ZERO).unwrap(); - } - - #[test] - #[should_panic] - fn l1_handler_tx_from_event_parse_missing_key_data() { - let from_address = selector!("from_address"); - let _to_address = selector!("to_address"); - let _selector = selector!("selector"); - let _nonce = FieldElement::ONE; - let _calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; - let transaction_hash = FieldElement::ZERO; - - let event = EmittedEvent { - from_address: felt!( - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" - ), - keys: vec![selector!("AnOtherUnexpectedEvent"), selector!("random_hash"), from_address], - data: vec![], - block_hash: selector!("block_hash"), - block_number: 0, - transaction_hash, - }; - - let _tx = l1_handler_tx_from_event(&event, FieldElement::ZERO).unwrap(); - } + // #[test] + // fn l1_handler_tx_from_event_parse_ok() { + // let from_address = selector!("from_address"); + // let to_address = selector!("to_address"); + // let selector = selector!("selector"); + // let chain_id = selector!("KATANA"); + // let nonce = FieldElement::ONE; + // let calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; + // let transaction_hash: FieldElement = compute_l1_handler_transaction_hash_felts( + // FieldElement::ZERO, + // to_address, + // selector, + // &stark_felt_to_field_element_array(&calldata), + // chain_id, + // nonce, + // ); + + // let event = EmittedEvent { + // from_address: felt!( + // "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" + // ), + // keys: vec![ + // selector!("MessageSentToAppchain"), + // selector!("random_hash"), + // from_address, + // to_address, + // ], + // data: vec![ + // selector, + // nonce, + // FieldElement::from(calldata.len() as u128), + // FieldElement::THREE, + // ], + // block_hash: selector!("block_hash"), + // block_number: 0, + // transaction_hash, + // }; + + // // let expected = L1HandlerTx { + // // inner: ApiL1HandlerTx { + // // transaction_hash: TransactionHash(transaction_hash.into()), + // // version: TransactionVersion(stark_felt!(0_u32)), + // // nonce: Nonce(nonce.into()), + // // contract_address: ContractAddress::try_from( + // // >::into(to_address), + // // ) + // // .unwrap(), + // // entry_point_selector: EntryPointSelector(selector.into()), + // // calldata: Calldata(calldata.into()), + // // }, + // // paid_l1_fee: 30000_u128, + // // }; + + // let tx = l1_handler_tx_from_event(&event, chain_id).unwrap(); + + // assert_eq!(tx.inner, expected.inner); + // } + + // #[test] + // #[should_panic] + // fn l1_handler_tx_from_event_parse_bad_selector() { + // let from_address = selector!("from_address"); + // let to_address = selector!("to_address"); + // let selector = selector!("selector"); + // let nonce = FieldElement::ONE; + // let calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; + // let transaction_hash = FieldElement::ZERO; + + // let event = EmittedEvent { + // from_address: felt!( + // "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" + // ), + // keys: vec![ + // selector!("AnOtherUnexpectedEvent"), + // selector!("random_hash"), + // from_address, + // to_address, + // ], + // data: vec![ + // selector, + // nonce, + // FieldElement::from(calldata.len() as u128), + // FieldElement::THREE, + // ], + // block_hash: selector!("block_hash"), + // block_number: 0, + // transaction_hash, + // }; + + // let _tx = l1_handler_tx_from_event(&event, FieldElement::ZERO).unwrap(); + // } + + // #[test] + // #[should_panic] + // fn l1_handler_tx_from_event_parse_missing_key_data() { + // let from_address = selector!("from_address"); + // let _to_address = selector!("to_address"); + // let _selector = selector!("selector"); + // let _nonce = FieldElement::ONE; + // let _calldata: Vec = vec![from_address.into(), FieldElement::THREE.into()]; + // let transaction_hash = FieldElement::ZERO; + + // let event = EmittedEvent { + // from_address: felt!( + // "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" + // ), + // keys: vec![selector!("AnOtherUnexpectedEvent"), selector!("random_hash"), + // from_address], data: vec![], + // block_hash: selector!("block_hash"), + // block_number: 0, + // transaction_hash, + // }; + + // let _tx = l1_handler_tx_from_event(&event, FieldElement::ZERO).unwrap(); + // } } diff --git a/crates/katana/core/src/utils/mod.rs b/crates/katana/core/src/utils/mod.rs index b03bb45e4c..9feccd99e0 100644 --- a/crates/katana/core/src/utils/mod.rs +++ b/crates/katana/core/src/utils/mod.rs @@ -19,7 +19,7 @@ use crate::constants::{ ERC20_CONTRACT, ERC20_CONTRACT_CLASS_HASH, ERC20_CONTRACT_COMPILED_CLASS_HASH, ERC20_DECIMALS_STORAGE_SLOT, ERC20_NAME_STORAGE_SLOT, ERC20_SYMBOL_STORAGE_SLOT, FEE_TOKEN_ADDRESS, OZ_V0_ACCOUNT_CONTRACT, OZ_V0_ACCOUNT_CONTRACT_CLASS_HASH, - OZ_V0_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, SEQUENCER_ADDRESS, UDC_ADDRESS, UDC_CLASS_HASH, + OZ_V0_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, UDC_ADDRESS, UDC_CLASS_HASH, UDC_COMPILED_CLASS_HASH, UDC_CONTRACT, }; diff --git a/crates/katana/core/tests/backend.rs b/crates/katana/core/tests/backend.rs index fcd1442c07..be08975e33 100644 --- a/crates/katana/core/tests/backend.rs +++ b/crates/katana/core/tests/backend.rs @@ -1,5 +1,6 @@ use katana_core::backend::config::{Environment, StarknetConfig}; use katana_core::backend::Backend; +use katana_provider::traits::block::{BlockNumberProvider, BlockProvider}; use starknet_api::block::BlockNumber; fn create_test_starknet_config() -> StarknetConfig { @@ -18,21 +19,23 @@ async fn create_test_backend() -> Backend { #[tokio::test] async fn test_creating_blocks() { - let starknet = create_test_backend().await; + let backend = create_test_backend().await; - assert_eq!(starknet.blockchain.storage.read().blocks.len(), 1); - assert_eq!(starknet.blockchain.storage.read().latest_number, 0); + let provider = backend.blockchain.provider(); - starknet.mine_empty_block().await; - starknet.mine_empty_block().await; + assert_eq!(BlockNumberProvider::latest_number(provider).unwrap(), 0); - assert_eq!(starknet.blockchain.storage.read().blocks.len(), 3); - assert_eq!(starknet.blockchain.storage.read().latest_number, 2); - assert_eq!(starknet.env.read().block.block_number, BlockNumber(2),); + backend.mine_empty_block(); + backend.mine_empty_block(); - let block0 = starknet.blockchain.storage.read().block_by_number(0).unwrap().clone(); - let block1 = starknet.blockchain.storage.read().block_by_number(1).unwrap().clone(); + assert_eq!(BlockNumberProvider::latest_number(provider).unwrap(), 2); + assert_eq!(backend.env.read().block.block_number, BlockNumber(2)); + + let block0 = BlockProvider::block_by_number(provider, 0).unwrap().unwrap(); + let block1 = BlockProvider::block_by_number(provider, 1).unwrap().unwrap(); + let block2 = BlockProvider::block_by_number(provider, 2).unwrap().unwrap(); assert_eq!(block0.header.number, 0); assert_eq!(block1.header.number, 1); + assert_eq!(block2.header.number, 2); } diff --git a/crates/katana/core/tests/sequencer.rs b/crates/katana/core/tests/sequencer.rs index ec8726027b..1782ce8355 100644 --- a/crates/katana/core/tests/sequencer.rs +++ b/crates/katana/core/tests/sequencer.rs @@ -1,18 +1,6 @@ -use std::time::Duration; - use katana_core::backend::config::{Environment, StarknetConfig}; -use katana_core::backend::storage::transaction::{DeclareTransaction, KnownTransaction}; use katana_core::sequencer::{KatanaSequencer, SequencerConfig}; -use katana_core::utils::contract::get_contract_class; -use starknet::core::types::FieldElement; -use starknet_api::core::{ClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::state::StorageKey; -use starknet_api::transaction::{ - DeclareTransaction as DeclareApiTransaction, DeclareTransactionV0V1, TransactionHash, -}; -use starknet_api::{patricia_key, stark_felt}; -use tokio::time::sleep; +use katana_provider::traits::block::BlockProvider; fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { ( @@ -32,48 +20,20 @@ async fn create_test_sequencer() -> KatanaSequencer { KatanaSequencer::new(sequencer_config, starknet_config).await } -fn create_declare_transaction(sender_address: ContractAddress) -> DeclareTransaction { - let compiled_class = - get_contract_class(include_str!("../contracts/compiled/test_contract.json")); - DeclareTransaction { - inner: DeclareApiTransaction::V0(DeclareTransactionV0V1 { - class_hash: ClassHash(stark_felt!("0x1234")), - nonce: Nonce(1u8.into()), - sender_address, - transaction_hash: TransactionHash(stark_felt!("0x6969")), - ..Default::default() - }), - compiled_class, - sierra_class: None, - } -} - #[tokio::test] async fn test_next_block_timestamp_in_past() { let sequencer = create_test_sequencer().await; - let block1 = sequencer.backend.mine_empty_block().await.block_number; - let block1_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block1) - .unwrap() - .header - .timestamp; - - sequencer.set_next_block_timestamp(block1_timestamp - 1000).await.unwrap(); - - let block2 = sequencer.backend.mine_empty_block().await.block_number; - let block2_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block2) - .unwrap() - .header - .timestamp; + let provider = sequencer.backend.blockchain.provider(); + let block1 = sequencer.backend.mine_empty_block().block_number; + + let block1_timestamp = + BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; + + sequencer.set_next_block_timestamp(block1_timestamp - 1000).unwrap(); + + let block2 = sequencer.backend.mine_empty_block().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"); } @@ -81,141 +41,59 @@ async fn test_next_block_timestamp_in_past() { #[tokio::test] async fn test_set_next_block_timestamp_in_future() { let sequencer = create_test_sequencer().await; - let block1 = sequencer.backend.mine_empty_block().await.block_number; - let block1_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block1) - .unwrap() - .header - .timestamp; - - sequencer.set_next_block_timestamp(block1_timestamp + 1000).await.unwrap(); - - let block2 = sequencer.backend.mine_empty_block().await.block_number; - let block2_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block2) - .unwrap() - .header - .timestamp; + let provider = sequencer.backend.blockchain.provider(); + let block1 = sequencer.backend.mine_empty_block().block_number; - assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); -} + let block1_timestamp = + BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; -#[tokio::test] -async fn test_increase_next_block_timestamp() { - let sequencer = create_test_sequencer().await; - let block1 = sequencer.backend.mine_empty_block().await.block_number; - let block1_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block1) - .unwrap() - .header - .timestamp; - - sequencer.increase_next_block_timestamp(1000).await.unwrap(); - - let block2 = sequencer.backend.mine_empty_block().await.block_number; - let block2_timestamp = sequencer - .backend - .blockchain - .storage - .read() - .block_by_number(block2) - .unwrap() - .header - .timestamp; + sequencer.set_next_block_timestamp(block1_timestamp + 1000).unwrap(); + + let block2 = sequencer.backend.mine_empty_block().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_storage_at_on_instant_mode() { +async fn test_increase_next_block_timestamp() { let sequencer = create_test_sequencer().await; - sequencer.backend.mine_empty_block().await; + let provider = sequencer.backend.blockchain.provider(); + let block1 = sequencer.backend.mine_empty_block().block_number; - let contract_address = ContractAddress(patricia_key!("0x1337")); - let key = StorageKey(patricia_key!("0x20")); - let val = stark_felt!("0xABC"); + let block1_timestamp = + BlockProvider::block(provider, block1.into()).unwrap().unwrap().header.timestamp; - { - 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.increase_next_block_timestamp(1000).unwrap(); - sequencer.set_storage_at(contract_address, key, val).await.unwrap(); + let block2 = sequencer.backend.mine_empty_block().block_number; + let block2_timestamp = + BlockProvider::block(provider, block2.into()).unwrap().unwrap().header.timestamp; - { - 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"); - } + assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); } -#[tokio::test(flavor = "multi_thread")] -async fn dump_and_load_state() { - let sequencer_old = create_test_sequencer().await; - assert_eq!(sequencer_old.block_number().await, 0); - - let declare_tx = create_declare_transaction(ContractAddress(patricia_key!( - sequencer_old.backend.accounts[0].address - ))); - - let tx_hash = declare_tx.inner.transaction_hash(); - - sequencer_old.add_declare_transaction(declare_tx); - - // wait for the tx to be picked up from the mempool, and executed and included in the next block - sleep(Duration::from_millis(500)).await; - - let tx_in_storage = sequencer_old.transaction(&tx_hash.0.into()).await.unwrap(); - - matches!(tx_in_storage, KnownTransaction::Included(_)); - assert_eq!(sequencer_old.block_number().await, 1); - - let serializable_state = sequencer_old - .backend - .state - .read() - .await - .dump_state() - .expect("must be able to serialize state"); - - assert!( - serializable_state.classes.get(&FieldElement::from_hex_be("0x1234").unwrap()).is_some(), - "class must be serialized" - ); - - // instantiate a new sequencer with the serialized state - let (sequencer_config, mut starknet_config) = create_test_sequencer_config(); - starknet_config.init_state = Some(serializable_state); - let sequencer_new = KatanaSequencer::new(sequencer_config, starknet_config).await; - - let old_contract = sequencer_old - .backend - .state - .write() - .await - .get_compiled_contract_class(&ClassHash(stark_felt!("0x1234"))) - .unwrap(); - - let new_contract = sequencer_new - .backend - .state - .write() - .await - .get_compiled_contract_class(&ClassHash(stark_felt!("0x1234"))) - .unwrap(); - - assert_eq!(old_contract, new_contract); -} +// #[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/executor/src/blockifier/mod.rs b/crates/katana/executor/src/blockifier/mod.rs index 233dcf59c6..8265dd2e6f 100644 --- a/crates/katana/executor/src/blockifier/mod.rs +++ b/crates/katana/executor/src/blockifier/mod.rs @@ -101,7 +101,7 @@ where let res = self .transactions .next() - .map(|tx| execute_tx(tx, &self.state, self.block_context, self.charge_fee))?; + .map(|tx| execute_tx(tx, self.state, self.block_context, self.charge_fee))?; match res { Ok(ref info) => { @@ -157,10 +157,10 @@ fn execute_tx( let res = match BlockifierTx::from(tx).0 { Transaction::AccountTransaction(tx) => { - tx.execute(&mut state.inner_mut(), block_context, charge_fee) + tx.execute(&mut state.inner(), block_context, charge_fee) } Transaction::L1HandlerTransaction(tx) => { - tx.execute(&mut state.inner_mut(), block_context, charge_fee) + tx.execute(&mut state.inner(), block_context, charge_fee) } }; @@ -177,5 +177,4 @@ pub struct PendingState { pub state: Arc>, /// The transactions that have been executed. pub executed_txs: RwLock>, - // pub executed_tx_results: RwLock>, } diff --git a/crates/katana/executor/src/blockifier/state.rs b/crates/katana/executor/src/blockifier/state.rs index ea2d77840d..3189a78fc5 100644 --- a/crates/katana/executor/src/blockifier/state.rs +++ b/crates/katana/executor/src/blockifier/state.rs @@ -2,16 +2,20 @@ use std::collections::HashMap; use blockifier::state::cached_state::CachedState; use blockifier::state::errors::StateError; -use blockifier::state::state_api::StateReader; +use blockifier::state::state_api::{State, StateReader}; use katana_primitives::contract::SierraClass; +use katana_primitives::FieldElement; use katana_provider::traits::contract::ContractClassProvider; -use katana_provider::traits::state::StateProvider; +use katana_provider::traits::state::{StateProvider, StateWriter}; +use parking_lot::{Mutex, RawMutex, RwLock}; use starknet_api::core::{ClassHash, CompiledClassHash, Nonce, PatriciaKey}; use starknet_api::hash::StarkHash; use starknet_api::patricia_key; use starknet_api::state::StorageKey; -use tokio::sync::RwLock as AsyncRwLock; +/// A state db only provide read access. +/// +/// This type implements the [`StateReader`] trait so that it can be used as a with [`CachedState`]. pub struct StateRefDb(Box); impl StateRefDb { @@ -89,44 +93,40 @@ impl StateReader for StateRefDb { } pub struct CachedStateWrapper { - inner: AsyncRwLock>, - sierra_class: AsyncRwLock>, + inner: Mutex>, + sierra_class: RwLock>, } impl CachedStateWrapper { pub fn new(db: S) -> Self { - Self { sierra_class: Default::default(), inner: AsyncRwLock::new(CachedState::new(db)) } + Self { sierra_class: Default::default(), inner: Mutex::new(CachedState::new(db)) } } pub fn reset_with_new_state(&self, db: S) { - *self.inner_mut() = CachedState::new(db); + *self.inner() = CachedState::new(db); self.sierra_class_mut().clear(); } - pub fn inner(&self) -> tokio::sync::RwLockReadGuard<'_, CachedState> { - tokio::task::block_in_place(|| self.inner.blocking_read()) - } - - pub fn inner_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, CachedState> { - tokio::task::block_in_place(|| self.inner.blocking_write()) + pub fn inner(&self) -> parking_lot::lock_api::MutexGuard<'_, RawMutex, CachedState> { + self.inner.lock() } pub fn sierra_class( &self, - ) -> tokio::sync::RwLockReadGuard< + ) -> parking_lot::RwLockReadGuard< '_, HashMap, > { - tokio::task::block_in_place(|| self.sierra_class.blocking_read()) + self.sierra_class.read() } pub fn sierra_class_mut( &self, - ) -> tokio::sync::RwLockWriteGuard< + ) -> parking_lot::RwLockWriteGuard< '_, HashMap, > { - tokio::task::block_in_place(|| self.sierra_class.blocking_write()) + self.sierra_class.write() } } @@ -138,8 +138,7 @@ where &self, hash: katana_primitives::contract::ClassHash, ) -> anyhow::Result> { - let Ok(class) = self.inner_mut().get_compiled_contract_class(&ClassHash(hash.into())) - else { + let Ok(class) = self.inner().get_compiled_contract_class(&ClassHash(hash.into())) else { return Ok(None); }; Ok(Some(class)) @@ -149,7 +148,7 @@ where &self, hash: katana_primitives::contract::ClassHash, ) -> anyhow::Result> { - let Ok(hash) = self.inner_mut().get_compiled_class_hash(ClassHash(hash.into())) else { + let Ok(hash) = self.inner().get_compiled_class_hash(ClassHash(hash.into())) else { return Ok(None); }; Ok(Some(hash.0.into())) @@ -176,7 +175,7 @@ where storage_key: katana_primitives::contract::StorageKey, ) -> anyhow::Result> { let Ok(value) = - self.inner_mut().get_storage_at(address.into(), StorageKey(patricia_key!(storage_key))) + self.inner().get_storage_at(address.into(), StorageKey(patricia_key!(storage_key))) else { return Ok(None); }; @@ -187,7 +186,7 @@ where &self, address: katana_primitives::contract::ContractAddress, ) -> anyhow::Result> { - let Ok(nonce) = self.inner_mut().get_nonce_at(address.into()) else { + let Ok(nonce) = self.inner().get_nonce_at(address.into()) else { return Ok(None); }; Ok(Some(nonce.0.into())) @@ -197,9 +196,46 @@ where &self, address: katana_primitives::contract::ContractAddress, ) -> anyhow::Result> { - let Ok(hash) = self.inner_mut().get_class_hash_at(address.into()) else { + let Ok(hash) = self.inner().get_class_hash_at(address.into()) else { return Ok(None); }; - Ok(Some(hash.0.into())) + + let hash = hash.0.into(); + if hash == FieldElement::ZERO { Ok(None) } else { Ok(Some(hash)) } + } +} + +impl StateWriter for CachedStateWrapper +where + Db: StateReader + Sync + Send, +{ + fn set_storage( + &self, + address: katana_primitives::contract::ContractAddress, + storage_key: katana_primitives::contract::StorageKey, + storage_value: katana_primitives::contract::StorageValue, + ) -> anyhow::Result<()> { + self.inner().set_storage_at( + address.into(), + StorageKey(patricia_key!(storage_key)), + storage_value.into(), + ); + Ok(()) + } + + fn set_nonce( + &self, + _address: katana_primitives::contract::ContractAddress, + _nonce: katana_primitives::contract::Nonce, + ) -> anyhow::Result<()> { + unimplemented!() + } + + fn set_class_hash_of_contract( + &self, + _address: katana_primitives::contract::ContractAddress, + _class_hash: katana_primitives::contract::ClassHash, + ) -> anyhow::Result<()> { + unimplemented!() } } diff --git a/crates/katana/executor/src/blockifier/utils.rs b/crates/katana/executor/src/blockifier/utils.rs index ea0a1a18e9..cf8edbffb8 100644 --- a/crates/katana/executor/src/blockifier/utils.rs +++ b/crates/katana/executor/src/blockifier/utils.rs @@ -28,6 +28,7 @@ use tracing::trace; use super::state::{CachedStateWrapper, StateRefDb}; use super::TransactionExecutor; +#[derive(Debug)] pub struct EntryPointCall { /// The address of the contract whose function you're calling. pub contract_address: ContractAddress, @@ -55,8 +56,8 @@ pub fn estimate_fee( block_context: BlockContext, state: Box, ) -> Result, TransactionExecutionError> { - let mut state = CachedStateWrapper::new(StateRefDb::from(state)); - let results = TransactionExecutor::new(&mut state, &block_context, false, transactions) + let state = CachedStateWrapper::new(StateRefDb::from(state)); + let results = TransactionExecutor::new(&state, &block_context, false, transactions) .with_error_log() .execute(); @@ -112,7 +113,7 @@ pub fn calculate_execution_fee( exec_info: &TransactionExecutionInfo, ) -> Result { let (l1_gas_usage, vm_resources) = extract_l1_gas_and_vm_usage(&exec_info.actual_resources); - let l1_gas_by_vm_usage = calculate_l1_gas_by_vm_usage(&block_context, &vm_resources)?; + let l1_gas_by_vm_usage = calculate_l1_gas_by_vm_usage(block_context, &vm_resources)?; let total_l1_gas_usage = l1_gas_usage as f64 + l1_gas_by_vm_usage; diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 7d3b3395e6..aa4662a627 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -4,8 +4,8 @@ use crate::contract::ContractAddress; use crate::transaction::{TxHash, TxWithHash}; use crate::FieldElement; -/// Block hash, number or tag. -pub type BlockId = starknet::core::types::BlockId; +pub type BlockIdOrTag = starknet::core::types::BlockId; +pub type BlockTag = starknet::core::types::BlockTag; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/katana/primitives/src/env.rs b/crates/katana/primitives/src/env.rs new file mode 100644 index 0000000000..8108e4a831 --- /dev/null +++ b/crates/katana/primitives/src/env.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; + +use crate::contract::ContractAddress; + +/// Block environment values. +#[derive(Debug, Clone)] +pub struct BlockEnv { + /// The block height. + pub number: u64, + /// The timestamp in seconds since the UNIX epoch. + pub timestamp: u64, + /// The block gas price in wei. + pub gas_price: u128, +} + +/// Starknet configuration values. +#[derive(Debug, Clone)] +pub struct CfgEnv { + /// The chain id. + pub chain_id: u64, + /// The contract address of the sequencer. + pub sequencer_address: ContractAddress, + /// The contract address of the fee token. + pub fee_token_address: ContractAddress, + /// The fee cost of the VM resources. + pub vm_resource_fee_cost: HashMap, + /// The maximum number of steps allowed for an invoke transaction. + pub invoke_tx_max_n_steps: u32, + /// The maximum number of steps allowed for transaction validation. + pub validate_max_n_steps: u32, + /// The maximum recursion depth allowed. + pub max_recursion_depth: usize, +} diff --git a/crates/katana/primitives/src/lib.rs b/crates/katana/primitives/src/lib.rs index 3eece65f47..e197639781 100644 --- a/crates/katana/primitives/src/lib.rs +++ b/crates/katana/primitives/src/lib.rs @@ -1,5 +1,6 @@ pub mod block; pub mod contract; +pub mod env; pub mod receipt; pub mod transaction; diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs index 1170ff99ac..000f7bf086 100644 --- a/crates/katana/primitives/src/receipt.rs +++ b/crates/katana/primitives/src/receipt.rs @@ -102,9 +102,3 @@ impl Receipt { } } } - -/// A helper enum for representing a receipt from a tx that may still be pending. -pub enum MaybePendingReceipt { - Pending(Receipt), - Receipt(Receipt), -} diff --git a/crates/katana/primitives/src/transaction.rs b/crates/katana/primitives/src/transaction.rs index 6a819c3e5c..f6ba646b3e 100644 --- a/crates/katana/primitives/src/transaction.rs +++ b/crates/katana/primitives/src/transaction.rs @@ -14,8 +14,32 @@ pub type TxHash = FieldElement; /// The sequential number for all the transactions.. pub type TxNumber = u64; -/// The finality status of a canonical transaction. -pub type TxFinalityStatus = starknet::core::types::TransactionFinalityStatus; +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Tx { + Invoke(InvokeTx), + Declare(DeclareTx), + L1Handler(L1HandlerTx), + DeployAccount(DeployAccountTx), +} + +pub enum TxRef<'a> { + Invoke(&'a InvokeTx), + Declare(&'a DeclareTx), + L1Handler(&'a L1HandlerTx), + DeployAccount(&'a DeployAccountTx), +} + +impl<'a> From> for Tx { + fn from(value: TxRef<'a>) -> Self { + match value { + TxRef::Invoke(tx) => Tx::Invoke(tx.clone()), + TxRef::Declare(tx) => Tx::Declare(tx.clone()), + TxRef::L1Handler(tx) => Tx::L1Handler(tx.clone()), + TxRef::DeployAccount(tx) => Tx::DeployAccount(tx.clone()), + } + } +} /// Represents a transaction that has all the necessary data to be executed. #[derive(Debug, Clone)] @@ -27,7 +51,7 @@ pub enum ExecutableTx { } impl ExecutableTx { - pub fn tx_ref(&self) -> TxRef { + pub fn tx_ref(&self) -> TxRef<'_> { match self { ExecutableTx::Invoke(tx) => TxRef::Invoke(tx), ExecutableTx::L1Handler(tx) => TxRef::L1Handler(tx), @@ -69,18 +93,6 @@ impl ExecutableTxWithHash { } } -// impl From for ExecutableTxWithHash { -// fn from(transaction: ExecutableTx) -> Self { -// let hash = match &transaction { -// ExecutableTx::L1Handler(tx) => tx.calculate_hash(), -// ExecutableTx::Invoke(tx) => tx.calculate_hash(false), -// ExecutableTx::Declare(tx) => tx.calculate_hash(false), -// ExecutableTx::DeployAccount(tx) => tx.calculate_hash(false), -// }; -// Self { hash, transaction } -// } -// } - #[derive(Debug, Clone, AsRef, Deref)] pub struct DeclareTxWithClass { /// The Sierra class, if any. @@ -103,33 +115,6 @@ impl DeclareTxWithClass { } } -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Tx { - Invoke(InvokeTx), - Declare(DeclareTx), - L1Handler(L1HandlerTx), - DeployAccount(DeployAccountTx), -} - -pub enum TxRef<'a> { - Invoke(&'a InvokeTx), - Declare(&'a DeclareTx), - L1Handler(&'a L1HandlerTx), - DeployAccount(&'a DeployAccountTx), -} - -impl<'a> From> for Tx { - fn from(value: TxRef<'a>) -> Self { - match value { - TxRef::Invoke(tx) => Tx::Invoke(tx.clone()), - TxRef::Declare(tx) => Tx::Declare(tx.clone()), - TxRef::L1Handler(tx) => Tx::L1Handler(tx.clone()), - TxRef::DeployAccount(tx) => Tx::DeployAccount(tx.clone()), - } - } -} - #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InvokeTx { @@ -272,11 +257,11 @@ impl DeployAccountTx { compute_deploy_account_v1_tx_hash( self.contract_address.into(), self.constructor_calldata.as_slice(), - self.class_hash.into(), - self.contract_address_salt.into(), + self.class_hash, + self.contract_address_salt, self.max_fee, - self.chain_id.into(), - self.nonce.into(), + self.chain_id, + self.nonce, is_query, ) } diff --git a/crates/katana/primitives/src/utils/contract.rs b/crates/katana/primitives/src/utils/contract.rs deleted file mode 100644 index 8ac0131427..0000000000 --- a/crates/katana/primitives/src/utils/contract.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::contract::CompiledContractClass; diff --git a/crates/katana/primitives/src/utils/transaction.rs b/crates/katana/primitives/src/utils/transaction.rs index ecb719885a..f1ed1e98ce 100644 --- a/crates/katana/primitives/src/utils/transaction.rs +++ b/crates/katana/primitives/src/utils/transaction.rs @@ -144,16 +144,13 @@ pub fn compute_l1_handler_tx_hash( chain_id: FieldElement, nonce: FieldElement, ) -> FieldElement { - // No fee on L2 for L1 handler transaction. - let fee = FieldElement::ZERO; - compute_hash_on_elements(&[ PREFIX_L1_HANDLER, version, contract_address, entry_point_selector, compute_hash_on_elements(calldata), - fee, + FieldElement::ZERO, // No fee on L2 for L1 handler tx chain_id, nonce, ]) diff --git a/crates/katana/rpc/rpc-types-builder/Cargo.toml b/crates/katana/rpc/rpc-types-builder/Cargo.toml index 92a178f668..a29f34ea29 100644 --- a/crates/katana/rpc/rpc-types-builder/Cargo.toml +++ b/crates/katana/rpc/rpc-types-builder/Cargo.toml @@ -1,5 +1,5 @@ [package] -description = "Builders for Katana types that are used in the RPC layer." +description = "Helper functions for building types used in Katana RPC." edition.workspace = true name = "katana-rpc-types-builder" version.workspace = true diff --git a/crates/katana/rpc/rpc-types-builder/src/block.rs b/crates/katana/rpc/rpc-types-builder/src/block.rs index b71a8c0801..f894bcb7e7 100644 --- a/crates/katana/rpc/rpc-types-builder/src/block.rs +++ b/crates/katana/rpc/rpc-types-builder/src/block.rs @@ -1,7 +1,6 @@ use anyhow::Result; use katana_primitives::block::BlockHashOrNumber; use katana_provider::traits::block::{BlockHashProvider, BlockProvider, BlockStatusProvider}; -use katana_provider::traits::transaction::TransactionsProviderExt; use katana_rpc_types::block::{BlockWithTxHashes, BlockWithTxs}; /// A builder for building RPC block types. diff --git a/crates/katana/rpc/rpc-types-builder/src/state_update.rs b/crates/katana/rpc/rpc-types-builder/src/state_update.rs index 65b8b5e5e6..b0f85cbc58 100644 --- a/crates/katana/rpc/rpc-types-builder/src/state_update.rs +++ b/crates/katana/rpc/rpc-types-builder/src/state_update.rs @@ -22,7 +22,7 @@ where P: BlockHashProvider + BlockNumberProvider + StateRootProvider + StateUpdateProvider, { /// Builds a state update for the given block. - pub fn build(self) -> Result, anyhow::Error> { + pub fn build(self) -> anyhow::Result> { let Some(block_hash) = BlockHashProvider::block_hash_by_id(&self.provider, self.block_id)? else { return Ok(None); diff --git a/crates/katana/rpc/src/api/starknet.rs b/crates/katana/rpc/src/api/starknet.rs index fea97c6974..c276d98924 100644 --- a/crates/katana/rpc/src/api/starknet.rs +++ b/crates/katana/rpc/src/api/starknet.rs @@ -1,7 +1,7 @@ use jsonrpsee::core::Error; use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::error::{CallError, ErrorObject}; -use katana_primitives::block::{BlockId, BlockNumber}; +use katana_primitives::block::{BlockIdOrTag, BlockNumber}; use katana_primitives::transaction::TxHash; use katana_primitives::FieldElement; use katana_rpc_types::block::{ @@ -17,7 +17,6 @@ use katana_rpc_types::transaction::{ }; use katana_rpc_types::{ContractClass, FeeEstimate, FeltAsHex, FunctionCall}; -// TODO: implement From for StarknetApiError #[derive(thiserror::Error, Clone, Copy, Debug)] pub enum StarknetApiError { #[error("Failed to write transaction")] @@ -96,7 +95,7 @@ pub trait StarknetApi { #[method(name = "getNonce")] async fn nonce( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result; @@ -107,12 +106,12 @@ pub trait StarknetApi { async fn transaction_by_hash(&self, transaction_hash: TxHash) -> Result; #[method(name = "getBlockTransactionCount")] - async fn block_transaction_count(&self, block_id: BlockId) -> Result; + async fn block_transaction_count(&self, block_id: BlockIdOrTag) -> Result; #[method(name = "getClassAt")] async fn class_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result; @@ -122,21 +121,24 @@ pub trait StarknetApi { #[method(name = "getBlockWithTxHashes")] async fn block_with_tx_hashes( &self, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result; - #[method(name = "getTransactionByBlockIdAndIndex")] + #[method(name = "getTransactionByBlockIdOrTagAndIndex")] async fn transaction_by_block_id_and_index( &self, - block_id: BlockId, - index: usize, + block_id: BlockIdOrTag, + index: u64, ) -> Result; #[method(name = "getBlockWithTxs")] - async fn block_with_txs(&self, block_id: BlockId) -> Result; + async fn block_with_txs( + &self, + block_id: BlockIdOrTag, + ) -> Result; #[method(name = "getStateUpdate")] - async fn state_update(&self, block_id: BlockId) -> Result; + async fn state_update(&self, block_id: BlockIdOrTag) -> Result; #[method(name = "getTransactionReceipt")] async fn transaction_receipt( @@ -147,14 +149,14 @@ pub trait StarknetApi { #[method(name = "getClassHashAt")] async fn class_hash_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result; #[method(name = "getClass")] async fn class( &self, - block_id: BlockId, + block_id: BlockIdOrTag, class_hash: FieldElement, ) -> Result; @@ -168,26 +170,29 @@ pub trait StarknetApi { async fn estimate_fee( &self, request: Vec, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result, Error>; #[method(name = "estimateMessageFee")] async fn estimate_message_fee( &self, message: MsgFromL1, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result; #[method(name = "call")] - async fn call(&self, request: FunctionCall, block_id: BlockId) - -> Result, Error>; + async fn call( + &self, + request: FunctionCall, + block_id: BlockIdOrTag, + ) -> Result, Error>; #[method(name = "getStorageAt")] async fn storage_at( &self, contract_address: FieldElement, key: FieldElement, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result; // Write API diff --git a/crates/katana/rpc/src/katana.rs b/crates/katana/rpc/src/katana.rs index a6519035f4..ffcc0c6e10 100644 --- a/crates/katana/rpc/src/katana.rs +++ b/crates/katana/rpc/src/katana.rs @@ -48,14 +48,14 @@ impl KatanaApiServer for KatanaApi { async fn set_storage_at( &self, - contract_address: FieldElement, - key: FieldElement, - value: FieldElement, + _contract_address: FieldElement, + _key: FieldElement, + _value: FieldElement, ) -> Result<(), Error> { // self.sequencer // .set_storage_at(contract_address.into(), key, value) // .await // .map_err(|_| Error::from(KatanaApiError::FailedToUpdateStorage)) - unimplemented!("temporarily removed") + Ok(()) } } diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index f101b9bb7a..7c9d7b7d0a 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -7,10 +7,11 @@ use katana_core::sequencer::KatanaSequencer; use katana_core::sequencer_error::SequencerError; use katana_core::utils::contract::legacy_inner_to_rpc_class; use katana_executor::blockifier::utils::EntryPointCall; -use katana_primitives::block::{BlockHashOrNumber, BlockId, PartialHeader}; +use katana_primitives::block::{BlockHashOrNumber, BlockIdOrTag, PartialHeader}; use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash}; use katana_primitives::FieldElement; -use katana_provider::traits::block::{BlockHashProvider, BlockNumberProvider}; +use katana_provider::traits::block::{BlockHashProvider, BlockIdReader, BlockNumberProvider}; +use katana_provider::traits::transaction::TransactionProvider; use katana_rpc_types::block::{ BlockHashAndNumber, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, PendingBlockWithTxHashes, PendingBlockWithTxs, @@ -47,7 +48,7 @@ impl StarknetApiServer for StarknetApi { async fn nonce( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result { let nonce = self @@ -76,7 +77,7 @@ impl StarknetApiServer for StarknetApi { Ok(tx.into()) } - async fn block_transaction_count(&self, block_id: BlockId) -> Result { + async fn block_transaction_count(&self, block_id: BlockIdOrTag) -> Result { let count = self .sequencer .block_tx_count(block_id) @@ -87,7 +88,7 @@ impl StarknetApiServer for StarknetApi { async fn class_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result { let class_hash = self @@ -109,11 +110,11 @@ impl StarknetApiServer for StarknetApi { async fn block_with_tx_hashes( &self, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result { let provider = self.sequencer.backend.blockchain.provider(); - if BlockId::Tag(BlockTag::Pending) == block_id { + if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { let pending_state = self.sequencer.pending_state().expect("pending state should exist"); let block_context = self.sequencer.backend.env.read().block.clone(); @@ -135,18 +136,12 @@ impl StarknetApiServer for StarknetApi { transactions, ))) } else { - let block_id = match block_id { - BlockId::Number(num) => BlockHashOrNumber::Num(num), - BlockId::Hash(hash) => BlockHashOrNumber::Hash(hash), - - BlockId::Tag(BlockTag::Latest) => BlockNumberProvider::latest_number(provider) - .map(BlockHashOrNumber::Num) - .map_err(|_| StarknetApiError::BlockNotFound)?, - - BlockId::Tag(BlockTag::Pending) => unreachable!("handled above"), - }; + let block_num = BlockIdReader::convert_block_id(provider, block_id) + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(BlockHashOrNumber::Num) + .ok_or(StarknetApiError::BlockNotFound)?; - katana_rpc_types_builder::BlockBuilder::new(block_id, provider) + katana_rpc_types_builder::BlockBuilder::new(block_num, provider) .build_with_tx_hash() .map_err(|_| StarknetApiError::UnexpectedError)? .map(MaybePendingBlockWithTxHashes::Block) @@ -156,25 +151,39 @@ impl StarknetApiServer for StarknetApi { async fn transaction_by_block_id_and_index( &self, - block_id: BlockId, - index: usize, + block_id: BlockIdOrTag, + index: u64, ) -> Result { - // let block = self.sequencer.block(block_id).await.ok_or(StarknetApiError::BlockNotFound)?; + // TEMP: have to handle pending tag independently for now + let tx = if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { + let Some(pending_state) = self.sequencer.pending_state() else { + return Err(StarknetApiError::BlockNotFound.into()); + }; - // let hash: FieldElement = block - // .transactions() - // .get(index) - // .map(|t| t.inner.hash()) - // .ok_or(StarknetApiError::InvalidTxnIndex)?; + let pending_txs = pending_state.executed_txs.read(); + pending_txs.iter().nth(index as usize).map(|(tx, _)| tx.clone()) + } else { + let provider = &self.sequencer.backend.blockchain.provider(); - // self.transaction_by_hash(hash).await - todo!() + let block_num = BlockIdReader::convert_block_id(provider, block_id) + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(BlockHashOrNumber::Num) + .ok_or(StarknetApiError::BlockNotFound)?; + + TransactionProvider::transaction_by_block_and_idx(provider, block_num, index) + .map_err(|_| StarknetApiError::UnexpectedError)? + }; + + Ok(tx.ok_or(StarknetApiError::InvalidTxnIndex)?.into()) } - async fn block_with_txs(&self, block_id: BlockId) -> Result { + async fn block_with_txs( + &self, + block_id: BlockIdOrTag, + ) -> Result { let provider = self.sequencer.backend.blockchain.provider(); - if BlockId::Tag(BlockTag::Pending) == block_id { + if BlockIdOrTag::Tag(BlockTag::Pending) == block_id { let pending_state = self.sequencer.pending_state().expect("pending state should exist"); let block_context = self.sequencer.backend.env.read().block.clone(); @@ -197,18 +206,12 @@ impl StarknetApiServer for StarknetApi { Ok(MaybePendingBlockWithTxs::Pending(PendingBlockWithTxs::new(header, transactions))) } else { - let block_id = match block_id { - BlockId::Number(num) => BlockHashOrNumber::Num(num), - BlockId::Hash(hash) => BlockHashOrNumber::Hash(hash), - - BlockId::Tag(BlockTag::Latest) => BlockNumberProvider::latest_number(provider) - .map(BlockHashOrNumber::Num) - .map_err(|_| StarknetApiError::BlockNotFound)?, - - BlockId::Tag(BlockTag::Pending) => unreachable!("handled above"), - }; + let block_num = BlockIdReader::convert_block_id(provider, block_id) + .map_err(|_| StarknetApiError::UnexpectedError)? + .map(BlockHashOrNumber::Num) + .ok_or(StarknetApiError::BlockNotFound)?; - katana_rpc_types_builder::BlockBuilder::new(block_id, provider) + katana_rpc_types_builder::BlockBuilder::new(block_num, provider) .build() .map_err(|_| StarknetApiError::UnexpectedError)? .map(MaybePendingBlockWithTxs::Block) @@ -216,17 +219,19 @@ impl StarknetApiServer for StarknetApi { } } - async fn state_update(&self, block_id: BlockId) -> Result { + async fn state_update(&self, block_id: BlockIdOrTag) -> Result { let provider = self.sequencer.backend.blockchain.provider(); let block_id = match block_id { - BlockId::Number(num) => BlockHashOrNumber::Num(num), - BlockId::Hash(hash) => BlockHashOrNumber::Hash(hash), + BlockIdOrTag::Number(num) => BlockHashOrNumber::Num(num), + BlockIdOrTag::Hash(hash) => BlockHashOrNumber::Hash(hash), - BlockId::Tag(BlockTag::Latest) => BlockNumberProvider::latest_number(provider) + BlockIdOrTag::Tag(BlockTag::Latest) => BlockNumberProvider::latest_number(provider) .map(BlockHashOrNumber::Num) .map_err(|_| StarknetApiError::BlockNotFound)?, - BlockId::Tag(BlockTag::Pending) => return Err(StarknetApiError::BlockNotFound.into()), + BlockIdOrTag::Tag(BlockTag::Pending) => { + return Err(StarknetApiError::BlockNotFound.into()); + } }; katana_rpc_types_builder::StateUpdateBuilder::new(block_id, provider) @@ -270,7 +275,7 @@ impl StarknetApiServer for StarknetApi { async fn class_hash_at( &self, - block_id: BlockId, + block_id: BlockIdOrTag, contract_address: FieldElement, ) -> Result { let hash = self @@ -287,7 +292,7 @@ impl StarknetApiServer for StarknetApi { async fn class( &self, - block_id: BlockId, + block_id: BlockIdOrTag, class_hash: FieldElement, ) -> Result { let class = self.sequencer.class(block_id, class_hash).map_err(|e| match e { @@ -308,30 +313,29 @@ impl StarknetApiServer for StarknetApi { } async fn events(&self, filter: EventFilterWithPage) -> Result { - todo!() - // let from_block = filter.event_filter.from_block.unwrap_or(BlockId::Number(0)); - // let to_block = filter.event_filter.to_block.unwrap_or(BlockId::Tag(BlockTag::Latest)); - - // let keys = filter.event_filter.keys; - // let keys = keys.filter(|keys| !(keys.len() == 1 && keys.is_empty())); - - // let events = self - // .sequencer - // .events( - // from_block, - // to_block, - // filter.event_filter.address, - // keys, - // filter.result_page_request.continuation_token, - // filter.result_page_request.chunk_size, - // ) - // .await - // .map_err(|e| match e { - // SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - // _ => StarknetApiError::UnexpectedError, - // })?; - - // Ok(events) + let from_block = filter.event_filter.from_block.unwrap_or(BlockIdOrTag::Number(0)); + let to_block = filter.event_filter.to_block.unwrap_or(BlockIdOrTag::Tag(BlockTag::Latest)); + + let keys = filter.event_filter.keys; + let keys = keys.filter(|keys| !(keys.len() == 1 && keys.is_empty())); + + let events = self + .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, + ) + .await + .map_err(|e| match e { + SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, + _ => StarknetApiError::UnexpectedError, + })?; + + Ok(events) } async fn pending_transactions(&self) -> Result, Error> { @@ -350,7 +354,7 @@ impl StarknetApiServer for StarknetApi { async fn call( &self, request: FunctionCall, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result, Error> { let request = EntryPointCall { calldata: request.calldata, @@ -372,7 +376,7 @@ impl StarknetApiServer for StarknetApi { &self, contract_address: FieldElement, key: FieldElement, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result { let value = self.sequencer.storage_at(contract_address.into(), key, block_id).map_err( |e| match e { @@ -410,7 +414,7 @@ impl StarknetApiServer for StarknetApi { async fn estimate_fee( &self, request: Vec, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result, Error> { let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().as_hex()) .map_err(|_| StarknetApiError::UnexpectedError)?; @@ -453,7 +457,7 @@ impl StarknetApiServer for StarknetApi { async fn estimate_message_fee( &self, message: MsgFromL1, - block_id: BlockId, + block_id: BlockIdOrTag, ) -> Result { let res = self .sequencer @@ -480,13 +484,14 @@ impl StarknetApiServer for StarknetApi { let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().as_hex()) .map_err(|_| StarknetApiError::UnexpectedError)?; - let is_valid = declare_transaction - .validate_compiled_class_hash() - .map_err(|_| StarknetApiError::InvalidContractClass)?; + // // validate compiled class hash + // let is_valid = declare_transaction + // .validate_compiled_class_hash() + // .map_err(|_| StarknetApiError::InvalidContractClass)?; - if !is_valid { - return Err(StarknetApiError::CompiledClassHashMismatch.into()); - } + // if !is_valid { + // return Err(StarknetApiError::CompiledClassHashMismatch.into()); + // } let tx = declare_transaction .try_into_tx_with_chain_id(chain_id) diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index 2341200685..ce11642b2d 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -14,7 +14,7 @@ use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; use katana_primitives::transaction::{TxHash, TxNumber, TxWithHash}; use katana_primitives::FieldElement; -use traits::block::{BlockStatusProvider, BlockWriter}; +use traits::block::{BlockIdReader, BlockStatusProvider, BlockWriter}; use traits::contract::{ContractClassProvider, ContractClassWriter}; use traits::state::{StateRootProvider, StateWriter}; use traits::transaction::TransactionStatusProvider; @@ -98,6 +98,8 @@ where } } +impl BlockIdReader for BlockchainProvider where Db: BlockNumberProvider {} + impl BlockStatusProvider for BlockchainProvider where Db: BlockStatusProvider, diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 74529c5f49..1415058c81 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -16,7 +16,7 @@ use katana_primitives::contract::{ }; use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; -use katana_primitives::transaction::{Tx, TxFinalityStatus, TxHash, TxNumber, TxWithHash}; +use katana_primitives::transaction::{Tx, TxHash, TxNumber, TxWithHash}; use parking_lot::RwLock; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; @@ -241,7 +241,7 @@ impl TransactionProvider for ForkedProvider { let id = offset + idx as usize; - let tx = self.storage.read().transactions.iter().nth(id).cloned().and_then(|tx| { + let tx = self.storage.read().transactions.get(id).cloned().and_then(|tx| { let hash = self.storage.read().transaction_hashes.get(&(id as u64)).copied()?; Some(TxWithHash { hash, transaction: tx }) }); @@ -297,7 +297,7 @@ impl TransactionStatusProvider for ForkedProvider { .read() .transaction_numbers .get(&hash) - .and_then(|n| self.storage.read().transaction_status.get(&n).copied()); + .and_then(|n| self.storage.read().transaction_status.get(n).copied()); Ok(status) } } @@ -433,6 +433,8 @@ impl BlockWriter for ForkedProvider { storage.transaction_block.extend(txs_block); storage.receipts.extend(receipts); + storage.state_update.insert(block_number, states.state_updates.clone()); + self.state.insert_updates(states); let snapshot = self.state.create_snapshot(); @@ -470,12 +472,7 @@ impl StateWriter for ForkedProvider { storage_key: katana_primitives::contract::StorageKey, storage_value: katana_primitives::contract::StorageValue, ) -> Result<()> { - self.state - .storage - .write() - .entry(address.into()) - .or_default() - .insert(storage_key, storage_value); + self.state.storage.write().entry(address).or_default().insert(storage_key, storage_value); Ok(()) } @@ -484,8 +481,7 @@ impl StateWriter for ForkedProvider { address: ContractAddress, class_hash: ClassHash, ) -> Result<()> { - self.state.contract_state.write().entry(address.into()).or_default().class_hash = - class_hash; + self.state.contract_state.write().entry(address).or_default().class_hash = class_hash; Ok(()) } @@ -494,7 +490,7 @@ impl StateWriter for ForkedProvider { address: ContractAddress, nonce: katana_primitives::contract::Nonce, ) -> Result<()> { - self.state.contract_state.write().entry(address.into()).or_default().nonce = nonce; + self.state.contract_state.write().entry(address).or_default().nonce = nonce; Ok(()) } } diff --git a/crates/katana/storage/provider/src/providers/in_memory/cache.rs b/crates/katana/storage/provider/src/providers/in_memory/cache.rs index 31c57df26e..0544012692 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/cache.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/cache.rs @@ -9,7 +9,7 @@ use katana_primitives::contract::{ }; use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; -use katana_primitives::transaction::{Tx, TxFinalityStatus, TxHash, TxNumber}; +use katana_primitives::transaction::{Tx, TxHash, TxNumber}; use parking_lot::RwLock; type ContractStorageMap = HashMap>; diff --git a/crates/katana/storage/provider/src/providers/in_memory/mod.rs b/crates/katana/storage/provider/src/providers/in_memory/mod.rs index df8dc1d1dd..477f8ba149 100644 --- a/crates/katana/storage/provider/src/providers/in_memory/mod.rs +++ b/crates/katana/storage/provider/src/providers/in_memory/mod.rs @@ -16,7 +16,7 @@ use katana_primitives::contract::{ }; use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; -use katana_primitives::transaction::{Tx, TxFinalityStatus, TxHash, TxNumber, TxWithHash}; +use katana_primitives::transaction::{Tx, TxHash, TxNumber, TxWithHash}; use parking_lot::RwLock; use self::cache::CacheDb; @@ -47,6 +47,12 @@ impl InMemoryProvider { } } +impl Default for InMemoryProvider { + fn default() -> Self { + Self::new() + } +} + impl BlockHashProvider for InMemoryProvider { fn latest_hash(&self) -> Result { Ok(self.storage.read().latest_block_hash) @@ -164,7 +170,7 @@ impl TransactionProvider for InMemoryProvider { fn transaction_by_hash(&self, hash: TxHash) -> Result> { let tx = self.storage.read().transaction_numbers.get(&hash).and_then(|num| { let transaction = self.storage.read().transactions.get(*num as usize)?.clone(); - let hash = self.storage.read().transaction_hashes.get(num)?.clone(); + let hash = *self.storage.read().transaction_hashes.get(num)?; Some(TxWithHash { hash, transaction }) }); Ok(tx) @@ -230,8 +236,8 @@ impl TransactionProvider for InMemoryProvider { let id = offset + idx as usize; - let tx = self.storage.read().transactions.iter().nth(id).cloned().and_then(|tx| { - let hash = self.storage.read().transaction_hashes.get(&(id as u64))?.clone(); + let tx = self.storage.read().transactions.get(id).cloned().and_then(|tx| { + let hash = *self.storage.read().transaction_hashes.get(&(id as u64))?; Some(TxWithHash { hash, transaction: tx }) }); @@ -287,7 +293,7 @@ impl TransactionStatusProvider for InMemoryProvider { .read() .transaction_numbers .get(&hash) - .and_then(|n| self.storage.read().transaction_status.get(&n).copied()); + .and_then(|n| self.storage.read().transaction_status.get(n).copied()); Ok(status) } } @@ -425,6 +431,8 @@ impl BlockWriter for InMemoryProvider { storage.transaction_status.extend(txs_status); storage.receipts.extend(receipts); + storage.state_update.insert(block_number, states.state_updates.clone()); + self.state.insert_updates(states); let snapshot = self.state.create_snapshot(); @@ -462,12 +470,7 @@ impl StateWriter for InMemoryProvider { storage_key: katana_primitives::contract::StorageKey, storage_value: katana_primitives::contract::StorageValue, ) -> Result<()> { - self.state - .storage - .write() - .entry(address.into()) - .or_default() - .insert(storage_key, storage_value); + self.state.storage.write().entry(address).or_default().insert(storage_key, storage_value); Ok(()) } @@ -476,8 +479,7 @@ impl StateWriter for InMemoryProvider { address: ContractAddress, class_hash: ClassHash, ) -> Result<()> { - self.state.contract_state.write().entry(address.into()).or_default().class_hash = - class_hash; + self.state.contract_state.write().entry(address).or_default().class_hash = class_hash; Ok(()) } @@ -486,7 +488,7 @@ impl StateWriter for InMemoryProvider { address: ContractAddress, nonce: katana_primitives::contract::Nonce, ) -> Result<()> { - self.state.contract_state.write().entry(address.into()).or_default().nonce = nonce; + self.state.contract_state.write().entry(address).or_default().nonce = nonce; Ok(()) } } diff --git a/crates/katana/storage/provider/src/traits/block.rs b/crates/katana/storage/provider/src/traits/block.rs index 7e0c171f73..8b7dfdb992 100644 --- a/crates/katana/storage/provider/src/traits/block.rs +++ b/crates/katana/storage/provider/src/traits/block.rs @@ -3,14 +3,42 @@ use std::ops::RangeInclusive; use anyhow::Result; use katana_db::models::block::StoredBlockBodyIndices; use katana_primitives::block::{ - Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithTxHashes, FinalityStatus, Header, - SealedBlockWithStatus, + Block, BlockHash, BlockHashOrNumber, BlockIdOrTag, BlockNumber, BlockTag, BlockWithTxHashes, + FinalityStatus, Header, SealedBlockWithStatus, }; use katana_primitives::receipt::Receipt; use katana_primitives::state::StateUpdatesWithDeclaredClasses; use super::transaction::{TransactionProvider, TransactionsProviderExt}; +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait BlockIdReader: BlockNumberProvider + Send + Sync { + /// Converts the block tag into its block number. + fn convert_block_id(&self, id: BlockIdOrTag) -> Result> { + match id { + BlockIdOrTag::Number(number) => Ok(Some(number)), + BlockIdOrTag::Hash(hash) => BlockNumberProvider::block_number_by_hash(self, hash), + + BlockIdOrTag::Tag(BlockTag::Latest) => { + BlockNumberProvider::latest_number(&self).map(Some) + } + + BlockIdOrTag::Tag(BlockTag::Pending) => { + if let Some((num, _)) = Self::pending_block_id(self)? { + Ok(Some(num)) + } else { + Ok(None) + } + } + } + } + + /// Retrieves the pending block number and hash. + fn pending_block_id(&self) -> Result> { + Ok(None) // Returns `None` for now + } +} + #[auto_impl::auto_impl(&, Box, Arc)] pub trait BlockHashProvider: Send + Sync { /// Retrieves the latest block hash. diff --git a/crates/katana/storage/provider/src/traits/env.rs b/crates/katana/storage/provider/src/traits/env.rs new file mode 100644 index 0000000000..73cc26cca9 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/env.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::env::BlockEnv; + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait BlockEnvProvider: Send + Sync { + fn env_at(&self, block_id: BlockHashOrNumber) -> Result; +} diff --git a/crates/katana/storage/provider/src/traits/mod.rs b/crates/katana/storage/provider/src/traits/mod.rs index 2598632fb1..762de20465 100644 --- a/crates/katana/storage/provider/src/traits/mod.rs +++ b/crates/katana/storage/provider/src/traits/mod.rs @@ -1,5 +1,6 @@ pub mod block; pub mod contract; +pub mod env; pub mod state; pub mod state_update; pub mod transaction; diff --git a/crates/katana/storage/provider/src/traits/state.rs b/crates/katana/storage/provider/src/traits/state.rs index 410bc5a16a..d201e33c59 100644 --- a/crates/katana/storage/provider/src/traits/state.rs +++ b/crates/katana/storage/provider/src/traits/state.rs @@ -3,7 +3,13 @@ use katana_primitives::block::BlockHashOrNumber; use katana_primitives::contract::{ClassHash, ContractAddress, Nonce, StorageKey, StorageValue}; use katana_primitives::FieldElement; -use super::contract::{ContractClassProvider, ContractClassWriter}; +use super::contract::ContractClassProvider; + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait StateRootProvider: Send + Sync { + /// Retrieves the state root of a block. + fn state_root(&self, block_id: BlockHashOrNumber) -> Result>; +} #[auto_impl::auto_impl(&, Box, Arc)] pub trait StateProvider: ContractClassProvider + Send + Sync { @@ -33,7 +39,7 @@ pub trait StateFactoryProvider: Send + Sync { // TEMP: added mainly for compatibility reason. it might be removed in the future. #[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateWriter: ContractClassWriter + Send + Sync { +pub trait StateWriter: Send + Sync { /// Sets the nonce of a contract. fn set_nonce(&self, address: ContractAddress, nonce: Nonce) -> Result<()>; @@ -52,9 +58,3 @@ pub trait StateWriter: ContractClassWriter + Send + Sync { class_hash: ClassHash, ) -> Result<()>; } - -#[auto_impl::auto_impl(&, Box, Arc)] -pub trait StateRootProvider: Send + Sync { - /// Retrieves the state root of a block. - fn state_root(&self, block_id: BlockHashOrNumber) -> Result>; -} diff --git a/crates/katana/storage/provider/src/traits/transaction.rs b/crates/katana/storage/provider/src/traits/transaction.rs index 58b810fcd3..8023d86568 100644 --- a/crates/katana/storage/provider/src/traits/transaction.rs +++ b/crates/katana/storage/provider/src/traits/transaction.rs @@ -3,7 +3,7 @@ use std::ops::Range; use anyhow::Result; use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockNumber, FinalityStatus}; use katana_primitives::receipt::Receipt; -use katana_primitives::transaction::{TxFinalityStatus, TxHash, TxNumber, TxWithHash}; +use katana_primitives::transaction::{TxHash, TxNumber, TxWithHash}; #[auto_impl::auto_impl(&, Box, Arc)] pub trait TransactionProvider: Send + Sync {