diff --git a/Cargo.lock b/Cargo.lock index cc1f9676ec..bfa303da28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,7 +1943,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -2113,6 +2113,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bonsai-trie" +version = "0.1.0" +source = "git+https://github.com/madara-alliance/bonsai-trie/?rev=56d7d62#56d7d62232fd72419f1d50de8bc747b70a9db68f" +dependencies = [ + "bitvec", + "derive_more 0.99.18", + "hashbrown 0.14.5", + "log", + "parity-scale-codec", + "rayon", + "serde", + "smallvec", + "starknet-types-core", +] + [[package]] name = "borsh" version = "1.5.1" @@ -6726,6 +6742,7 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", + "rayon", "serde", ] @@ -7208,7 +7225,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.51.1", + "windows-core 0.52.0", ] [[package]] @@ -8170,6 +8187,7 @@ dependencies = [ "katana-primitives", "katana-provider", "katana-tasks", + "katana-trie", "lazy_static", "metrics", "num-traits 0.2.19", @@ -8178,6 +8196,7 @@ dependencies = [ "serde", "serde_json", "starknet 0.12.0", + "starknet-types-core", "tempfile", "thiserror", "tokio", @@ -8191,9 +8210,11 @@ version = "1.0.0-rc.0" dependencies = [ "anyhow", "arbitrary", + "bitvec", "criterion", "dojo-metrics", "katana-primitives", + "katana-trie", "metrics", "page_size", "parking_lot 0.12.3", @@ -8202,7 +8223,9 @@ dependencies = [ "roaring", "serde", "serde_json", + "smallvec", "starknet 0.12.0", + "starknet-types-core", "tempfile", "thiserror", "tracing", @@ -8346,10 +8369,12 @@ dependencies = [ "alloy-primitives", "anyhow", "auto_impl", + "bitvec", "futures", "katana-db", "katana-primitives", "katana-runner", + "katana-trie", "lazy_static", "parking_lot 0.12.3", "rand 0.8.5", @@ -8357,6 +8382,7 @@ dependencies = [ "rstest_reuse", "serde_json", "starknet 0.12.0", + "starknet-types-core", "tempfile", "thiserror", "tokio", @@ -8509,6 +8535,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "katana-trie" +version = "1.0.0-rc.0" +dependencies = [ + "anyhow", + "bitvec", + "bonsai-trie", + "katana-primitives", + "serde", + "slab", + "starknet 0.12.0", + "starknet-types-core", + "thiserror", +] + [[package]] name = "keccak" version = "0.1.5" @@ -8599,9 +8640,9 @@ dependencies = [ [[package]] name = "lambdaworks-crypto" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb5d4f22241504f7c7b8d2c3a7d7835d7c07117f10bff2a7d96a9ef6ef217c3" +checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ "lambdaworks-math", "serde", @@ -8611,9 +8652,9 @@ dependencies = [ [[package]] name = "lambdaworks-math" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358e172628e713b80a530a59654154bfc45783a6ed70ea284839800cebdf8f97" +checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" dependencies = [ "serde", "serde_json", @@ -11032,7 +11073,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.10.5", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -11073,7 +11114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.77", @@ -14064,9 +14105,9 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b889ee5734db8b3c8a6551135c16764bf4ce1ab4955fffbb2ac5b6706542b64" +checksum = "fa1b9e01ccb217ab6d475c5cda05dbb22c30029f7bb52b192a010a00d77a3d74" dependencies = [ "arbitrary", "lambdaworks-crypto", @@ -14075,6 +14116,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits 0.2.19", + "parity-scale-codec", "serde", ] @@ -16525,6 +16567,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.57.0" diff --git a/Cargo.toml b/Cargo.toml index b5a19cedad..d9d0542ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "crates/katana/storage/db", "crates/katana/storage/provider", "crates/katana/tasks", + "crates/katana/trie", "crates/metrics", "crates/saya/core", "crates/saya/provider", @@ -100,6 +101,7 @@ katana-rpc-types-builder = { path = "crates/katana/rpc/rpc-types-builder" } katana-runner = { path = "crates/katana/runner" } katana-slot-controller = { path = "crates/katana/controller" } katana-tasks = { path = "crates/katana/tasks" } +katana-trie = { path = "crates/katana/trie" } # torii torii-client = { path = "crates/torii/client" } @@ -254,4 +256,6 @@ alloy-transport = { version = "0.3", default-features = false } starknet = "0.12.0" starknet-crypto = "0.7.1" -starknet-types-core = { version = "0.1.6", features = [ "arbitrary" ] } +starknet-types-core = { version = "0.1.7", features = [ "arbitrary", "hash" ] } + +bitvec = "1.0.1" diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index ae9cd35dd3..2d166c6795 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -13,6 +13,7 @@ katana-pool.workspace = true katana-primitives.workspace = true katana-provider.workspace = true katana-tasks.workspace = true +katana-trie.workspace = true anyhow.workspace = true async-trait.workspace = true @@ -27,6 +28,7 @@ reqwest.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true +starknet-types-core.workspace = true thiserror.workspace = true tokio.workspace = true tracing.workspace = true diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 82b0d980ef..8e15426b98 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -2,16 +2,21 @@ use std::sync::Arc; use katana_executor::{ExecutionOutput, ExecutionResult, ExecutorFactory}; use katana_primitives::block::{ - Block, FinalityStatus, GasPrices, Header, SealedBlock, SealedBlockWithStatus, + FinalityStatus, Header, PartialHeader, SealedBlock, SealedBlockWithStatus, }; use katana_primitives::chain_spec::ChainSpec; use katana_primitives::da::L1DataAvailabilityMode; use katana_primitives::env::BlockEnv; -use katana_primitives::receipt::Receipt; +use katana_primitives::receipt::{Event, ReceiptWithTxHash}; +use katana_primitives::state::{compute_state_diff_hash, StateUpdates}; use katana_primitives::transaction::{TxHash, TxWithHash}; use katana_primitives::Felt; use katana_provider::traits::block::{BlockHashProvider, BlockWriter}; +use katana_provider::traits::trie::{ClassTrieWriter, ContractTrieWriter}; +use katana_trie::compute_merkle_root; use parking_lot::RwLock; +use starknet::macros::short_string; +use starknet_types_core::hash::{self, StarkHash}; use tracing::info; pub mod contract; @@ -50,9 +55,9 @@ impl Backend { // only include successful transactions in the block for (tx, res) in execution_output.transactions { if let ExecutionResult::Success { receipt, trace, .. } = res { - txs.push(tx); + receipts.push(ReceiptWithTxHash::new(tx.hash, receipt)); traces.push(trace); - receipts.push(receipt); + txs.push(tx); } } @@ -60,10 +65,19 @@ impl Backend { let tx_hashes = txs.iter().map(|tx| tx.hash).collect::>(); // create a new block and compute its commitment - let block = self.commit_block(block_env, txs, &receipts)?; + let block = self.commit_block( + block_env.clone(), + execution_output.states.state_updates.clone(), + txs, + &receipts, + )?; + let block = SealedBlockWithStatus { block, status: FinalityStatus::AcceptedOnL2 }; let block_number = block.block.header.number; + // TODO: maybe should change the arguments for insert_block_with_states_and_receipts to + // accept ReceiptWithTxHash instead to avoid this conversion. + let receipts = receipts.into_iter().map(|r| r.receipt).collect::>(); self.blockchain.provider().insert_block_with_states_and_receipts( block, execution_output.states, @@ -101,41 +115,154 @@ impl Backend { fn commit_block( &self, - block_env: &BlockEnv, + block_env: BlockEnv, + state_updates: StateUpdates, transactions: Vec, - receipts: &[Receipt], + receipts: &[ReceiptWithTxHash], ) -> Result { - // get the hash of the latest committed block let parent_hash = self.blockchain.provider().latest_hash()?; - let events_count = receipts.iter().map(|r| r.events().len() as u32).sum::(); - let transaction_count = transactions.len() as u32; - - let l1_gas_prices = - GasPrices { eth: block_env.l1_gas_prices.eth, strk: block_env.l1_gas_prices.strk }; - let l1_data_gas_prices = GasPrices { - eth: block_env.l1_data_gas_prices.eth, - strk: block_env.l1_data_gas_prices.strk, + let partial_header = PartialHeader { + parent_hash, + number: block_env.number, + timestamp: block_env.timestamp, + protocol_version: self.chain_spec.version.clone(), + sequencer_address: block_env.sequencer_address, + l1_gas_prices: block_env.l1_gas_prices, + l1_data_gas_prices: block_env.l1_data_gas_prices, + l1_da_mode: L1DataAvailabilityMode::Calldata, }; + let block = UncommittedBlock::new( + partial_header, + transactions, + receipts, + &state_updates, + &self.blockchain.provider(), + ) + .commit(); + Ok(block) + } +} + +#[derive(Debug, Clone)] +pub struct UncommittedBlock<'a, P> +where + P: ClassTrieWriter + ContractTrieWriter, +{ + header: PartialHeader, + transactions: Vec, + receipts: &'a [ReceiptWithTxHash], + state_updates: &'a StateUpdates, + trie_provider: P, +} + +impl<'a, P> UncommittedBlock<'a, P> +where + P: ClassTrieWriter + ContractTrieWriter, +{ + pub fn new( + header: PartialHeader, + transactions: Vec, + receipts: &'a [ReceiptWithTxHash], + state_updates: &'a StateUpdates, + trie_provider: P, + ) -> Self { + Self { header, transactions, receipts, state_updates, trie_provider } + } + + pub fn commit(self) -> SealedBlock { + // get the hash of the latest committed block + let parent_hash = self.header.parent_hash; + let events_count = self.receipts.iter().map(|r| r.events().len() as u32).sum::(); + let transaction_count = self.transactions.len() as u32; + let state_diff_length = self.state_updates.len() as u32; + + let state_root = self.compute_new_state_root(); + let transactions_commitment = self.compute_transaction_commitment(); + let events_commitment = self.compute_event_commitment(); + let receipts_commitment = self.compute_receipt_commitment(); + let state_diff_commitment = self.compute_state_diff_commitment(); + let header = Header { + state_root, parent_hash, events_count, - l1_gas_prices, + state_diff_length, transaction_count, - l1_data_gas_prices, - state_root: Felt::ZERO, - number: block_env.number, - events_commitment: Felt::ZERO, - timestamp: block_env.timestamp, - receipts_commitment: Felt::ZERO, - state_diff_commitment: Felt::ZERO, - transactions_commitment: Felt::ZERO, - l1_da_mode: L1DataAvailabilityMode::Calldata, - sequencer_address: block_env.sequencer_address, - protocol_version: self.chain_spec.version.clone(), + events_commitment, + receipts_commitment, + state_diff_commitment, + transactions_commitment, + number: self.header.number, + timestamp: self.header.timestamp, + l1_da_mode: self.header.l1_da_mode, + l1_gas_prices: self.header.l1_gas_prices, + l1_data_gas_prices: self.header.l1_data_gas_prices, + sequencer_address: self.header.sequencer_address, + protocol_version: self.header.protocol_version, }; - let sealed = Block { header, body: transactions }.seal(); - Ok(sealed) + let hash = header.compute_hash(); + + SealedBlock { hash, header, body: self.transactions } + } + + fn compute_transaction_commitment(&self) -> Felt { + let tx_hashes = self.transactions.iter().map(|t| t.hash).collect::>(); + compute_merkle_root::(&tx_hashes).unwrap() + } + + fn compute_receipt_commitment(&self) -> Felt { + let receipt_hashes = self.receipts.iter().map(|r| r.compute_hash()).collect::>(); + compute_merkle_root::(&receipt_hashes).unwrap() + } + + fn compute_state_diff_commitment(&self) -> Felt { + compute_state_diff_hash(self.state_updates.clone()) + } + + fn compute_event_commitment(&self) -> Felt { + // h(emitter_address, tx_hash, h(keys), h(data)) + fn event_hash(tx: TxHash, event: &Event) -> Felt { + let keys_hash = hash::Poseidon::hash_array(&event.keys); + let data_hash = hash::Poseidon::hash_array(&event.data); + hash::Poseidon::hash_array(&[tx, event.from_address.into(), keys_hash, data_hash]) + } + + // the iterator will yield all events from all the receipts, each one paired with the + // transaction hash that emitted it: (tx hash, event). + let events = self.receipts.iter().flat_map(|r| r.events().iter().map(|e| (r.tx_hash, e))); + + let mut hashes = Vec::new(); + for (tx, event) in events { + let event_hash = event_hash(tx, event); + hashes.push(event_hash); + } + + // compute events commitment + compute_merkle_root::(&hashes).unwrap() + } + + // state_commitment = hPos("STARKNET_STATE_V0", contract_trie_root, class_trie_root) + fn compute_new_state_root(&self) -> Felt { + let class_trie_root = ClassTrieWriter::insert_updates( + &self.trie_provider, + self.header.number, + &self.state_updates.declared_classes, + ) + .unwrap(); + + let contract_trie_root = ContractTrieWriter::insert_updates( + &self.trie_provider, + self.header.number, + self.state_updates, + ) + .unwrap(); + + hash::Poseidon::hash_array(&[ + short_string!("STARKNET_STATE_V0"), + contract_trie_root, + class_trie_root, + ]) } } diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index 327c831375..f14f59d70d 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -20,6 +20,7 @@ use katana_provider::traits::transaction::{ ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionTraceProvider, TransactionsProviderExt, }; +use katana_provider::traits::trie::{ClassTrieWriter, ContractTrieWriter}; use katana_provider::BlockchainProvider; use num_traits::ToPrimitive; use starknet::core::types::{BlockStatus, MaybePendingBlockWithTxHashes}; @@ -43,6 +44,8 @@ pub trait Database: + ContractClassWriter + StateFactoryProvider + BlockEnvProvider + + ClassTrieWriter + + ContractTrieWriter + 'static + Send + Sync @@ -64,6 +67,8 @@ impl Database for T where + ContractClassWriter + StateFactoryProvider + BlockEnvProvider + + ClassTrieWriter + + ContractTrieWriter + 'static + Send + Sync diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 2462efd739..4d1b989679 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -1,4 +1,5 @@ -use starknet::core::crypto::compute_hash_on_elements; +use starknet::core::utils::cairo_short_string_to_felt; +use starknet::macros::short_string; use crate::contract::ContractAddress; use crate::da::L1DataAvailabilityMode; @@ -9,6 +10,11 @@ use crate::Felt; pub type BlockIdOrTag = starknet::core::types::BlockId; pub type BlockTag = starknet::core::types::BlockTag; +/// Block number type. +pub type BlockNumber = u64; +/// Block hash type. +pub type BlockHash = Felt; + #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum BlockHashOrNumber { @@ -25,11 +31,6 @@ impl std::fmt::Display for BlockHashOrNumber { } } -/// Block number type. -pub type BlockNumber = u64; -/// Block hash type. -pub type BlockHash = Felt; - /// Finality status of a canonical block. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] @@ -73,6 +74,8 @@ impl GasPrices { } } +// uncommited header -> header (what is stored in the database) + /// Represents a block header. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] @@ -85,9 +88,10 @@ pub struct Header { pub receipts_commitment: Felt, pub events_commitment: Felt, pub state_root: Felt, - pub timestamp: u64, pub transaction_count: u32, pub events_count: u32, + pub state_diff_length: u32, + pub timestamp: u64, pub sequencer_address: ContractAddress, pub l1_gas_prices: GasPrices, pub l1_data_gas_prices: GasPrices, @@ -95,12 +99,113 @@ pub struct Header { pub protocol_version: ProtocolVersion, } +impl Header { + /// Computes the block hash. + /// + /// A block hash is defined as the Poseidon hash of the header’s fields, as follows: + /// + /// h(𝐵) = h( + /// "STARKNET_BLOCK_HASH0", + /// block_number, + /// global_state_root, + /// sequencer_address, + /// block_timestamp, + /// transaction_count || event_count || state_diff_length || l1_da_mode, + /// state_diff_commitment, + /// transactions_commitment + /// events_commitment, + /// receipts_commitment + /// l1_gas_price_in_wei, + /// l1_gas_price_in_fri, + /// l1_data_gas_price_in_wei, + /// l1_data_gas_price_in_fri + /// protocol_version, + /// 0, + /// parent_block_hash + /// ) + /// + /// Based on StarkWare's [Sequencer implementation]. + /// + /// [sequencer implementation]: https://github.com/starkware-libs/sequencer/blob/bb361ec67396660d5468fd088171913e11482708/crates/starknet_api/src/block_hash/block_hash_calculator.rs#l62-l93 + pub fn compute_hash(&self) -> Felt { + use starknet_types_core::hash::{Poseidon, StarkHash}; + + let concant = Self::concat_counts( + self.transaction_count, + self.events_count, + self.state_diff_length, + self.l1_da_mode, + ); + + Poseidon::hash_array(&[ + short_string!("STARKNET_BLOCK_HASH0"), + self.number.into(), + self.state_root, + self.sequencer_address.into(), + self.timestamp.into(), + concant, + self.state_diff_commitment, + self.transactions_commitment, + self.events_commitment, + self.receipts_commitment, + self.l1_gas_prices.eth.into(), + self.l1_gas_prices.strk.into(), + self.l1_data_gas_prices.eth.into(), + self.l1_data_gas_prices.strk.into(), + cairo_short_string_to_felt(&self.protocol_version.to_string()).unwrap(), + Felt::ZERO, + self.parent_hash, + ]) + } + + // Concantenate the transaction_count, event_count and state_diff_length, and l1_da_mode into a + // single felt. + // + // A single felt: + // + // +-------------------+----------------+----------------------+--------------+------------+ + // | transaction_count | event_count | state_diff_length | L1 DA mode | padding | + // | (64 bits) | (64 bits) | (64 bits) | (1 bit) | (63 bit) | + // +-------------------+----------------+----------------------+--------------+------------+ + // + // where, L1 DA mode is 0 for calldata, and 1 for blob. + // + // Based on https://github.com/starkware-libs/sequencer/blob/bb361ec67396660d5468fd088171913e11482708/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L135-L164 + fn concat_counts( + transaction_count: u32, + event_count: u32, + state_diff_length: u32, + l1_data_availability_mode: L1DataAvailabilityMode, + ) -> Felt { + fn to_64_bits(num: u32) -> [u8; 8] { + (num as u64).to_be_bytes() + } + + let l1_data_availability_byte: u8 = match l1_data_availability_mode { + L1DataAvailabilityMode::Calldata => 0, + L1DataAvailabilityMode::Blob => 0b_1000_0000, + }; + + let concat_bytes = [ + to_64_bits(transaction_count).as_slice(), + to_64_bits(event_count).as_slice(), + to_64_bits(state_diff_length).as_slice(), + &[l1_data_availability_byte], + &[0_u8; 7], // zero padding + ] + .concat(); + + Felt::from_bytes_be_slice(concat_bytes.as_slice()) + } +} + impl Default for Header { fn default() -> Self { Self { timestamp: 0, events_count: 0, transaction_count: 0, + state_diff_length: 0, state_root: Felt::ZERO, events_commitment: Felt::ZERO, number: BlockNumber::default(), @@ -117,23 +222,6 @@ impl Default for Header { } } -impl Header { - /// Computes the hash of the header. - pub fn compute_hash(&self) -> Felt { - compute_hash_on_elements(&vec![ - self.number.into(), // block number - Felt::ZERO, // state root - self.sequencer_address.into(), // sequencer address - self.timestamp.into(), // block timestamp - Felt::ZERO, // transaction commitment - Felt::ZERO, // event commitment - Felt::ZERO, // protocol version - Felt::ZERO, // extra data - self.parent_hash, // parent hash - ]) - } -} - /// Represents a Starknet full block. #[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -227,3 +315,24 @@ pub struct ExecutableBlock { pub header: PartialHeader, pub body: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + use crate::felt; + + #[test] + fn header_concat_counts() { + let expected = felt!("0x6400000000000000c8000000000000012c0000000000000000"); + let actual = Header::concat_counts(100, 200, 300, L1DataAvailabilityMode::Calldata); + assert_eq!(actual, expected); + + let expected = felt!("0x1000000000000000200000000000000038000000000000000"); + let actual = Header::concat_counts(1, 2, 3, L1DataAvailabilityMode::Blob); + assert_eq!(actual, expected); + + let expected = felt!("0xffffffff000000000000000000000000000000000000000000000000"); + let actual = Header::concat_counts(0xFFFFFFFF, 0, 0, L1DataAvailabilityMode::Calldata); + assert_eq!(actual, expected); + } +} diff --git a/crates/katana/primitives/src/chain_spec.rs b/crates/katana/primitives/src/chain_spec.rs index 5d5deaee54..0303433ccd 100644 --- a/crates/katana/primitives/src/chain_spec.rs +++ b/crates/katana/primitives/src/chain_spec.rs @@ -53,6 +53,7 @@ pub struct FeeContracts { impl ChainSpec { pub fn block(&self) -> Block { let header = Header { + state_diff_length: 0, protocol_version: self.version.clone(), number: self.genesis.number, timestamp: self.genesis.timestamp, @@ -371,6 +372,7 @@ mod tests { // setup expected storage values let expected_block = Block { header: Header { + state_diff_length: 0, events_commitment: Felt::ZERO, receipts_commitment: Felt::ZERO, state_diff_commitment: Felt::ZERO, diff --git a/crates/katana/primitives/src/receipt.rs b/crates/katana/primitives/src/receipt.rs index a3d23025fc..abe0dbec3c 100644 --- a/crates/katana/primitives/src/receipt.rs +++ b/crates/katana/primitives/src/receipt.rs @@ -1,8 +1,14 @@ +use std::iter; + use alloy_primitives::B256; +use derive_more::{AsRef, Deref}; +use starknet::core::utils::starknet_keccak; +use starknet_types_core::hash::{self, StarkHash}; use crate::contract::ContractAddress; use crate::fee::TxFeeInfo; use crate::trace::TxResources; +use crate::transaction::TxHash; use crate::Felt; #[derive(Debug, Clone, PartialEq, Eq)] @@ -170,3 +176,70 @@ impl Receipt { } } } + +#[derive(Debug, Clone, AsRef, Deref, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ReceiptWithTxHash { + /// The hash of the transaction. + pub tx_hash: TxHash, + /// The raw transaction. + #[deref] + #[as_ref] + pub receipt: Receipt, +} + +impl ReceiptWithTxHash { + pub fn new(hash: TxHash, receipt: Receipt) -> Self { + Self { tx_hash: hash, receipt } + } + + /// Computes the hash of the receipt. This is used for computing the receipts commitment. + /// + /// See the Starknet [docs] for reference. + /// + /// [docs]: https://docs.starknet.io/architecture-and-concepts/network-architecture/block-structure/#receipt_hash + pub fn compute_hash(&self) -> Felt { + let messages_hash = self.compute_messages_to_l1_hash(); + let revert_reason_hash = if let Some(reason) = self.revert_reason() { + starknet_keccak(reason.as_bytes()) + } else { + Felt::ZERO + }; + + hash::Poseidon::hash_array(&[ + self.tx_hash, + self.receipt.fee().overall_fee.into(), + messages_hash, + revert_reason_hash, + Felt::ZERO, // L2 gas consumption. + self.receipt.fee().gas_consumed.into(), + // self.receipt.fee().l1_data_gas.into(), + ]) + } + + // H(n, from, to, H(payload), ...), where n, is the total number of messages, the payload is + // prefixed by its length, and h is the Poseidon hash function. + fn compute_messages_to_l1_hash(&self) -> Felt { + let messages = self.messages_sent(); + let messages_len = messages.len(); + + // Allocate all the memory in advance; times 3 because [ from, to, h(payload) ] + let mut accumulator: Vec = Vec::with_capacity((messages_len * 3) + 1); + accumulator.push(Felt::from(messages_len)); + + let elements = messages.iter().fold(accumulator, |mut acc, msg| { + // Compute the payload hash; h(n, payload_1, ..., payload_n) + let len = Felt::from(msg.payload.len()); + let payload = iter::once(len).chain(msg.payload.clone()).collect::>(); + let payload_hash = hash::Poseidon::hash_array(&payload); + + acc.push(msg.from_address.into()); + acc.push(msg.to_address); + acc.push(payload_hash); + + acc + }); + + hash::Poseidon::hash_array(&elements) + } +} diff --git a/crates/katana/primitives/src/state.rs b/crates/katana/primitives/src/state.rs index dc70a7805f..3ec9e65985 100644 --- a/crates/katana/primitives/src/state.rs +++ b/crates/katana/primitives/src/state.rs @@ -1,7 +1,12 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::iter; + +use starknet::macros::short_string; +use starknet_types_core::hash::{self, StarkHash}; use crate::class::{ClassHash, CompiledClass, CompiledClassHash, FlattenedSierraClass}; use crate::contract::{ContractAddress, Nonce, StorageKey, StorageValue}; +use crate::Felt; /// State updates. /// @@ -24,6 +29,25 @@ pub struct StateUpdates { pub replaced_classes: BTreeMap, } +impl StateUpdates { + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + let mut len: usize = 0; + + len += self.deployed_contracts.len(); + len += self.replaced_classes.len(); + len += self.declared_classes.len(); + len += self.deprecated_declared_classes.len(); + len += self.nonce_updates.len(); + + for updates in self.storage_updates.values() { + len += updates.len(); + } + + len + } +} + /// State update with declared classes definition. #[derive(Debug, Default, Clone)] pub struct StateUpdatesWithDeclaredClasses { @@ -34,3 +58,50 @@ pub struct StateUpdatesWithDeclaredClasses { /// A mapping of class hashes to their compiled classes definition. pub declared_compiled_classes: BTreeMap, } + +pub fn compute_state_diff_hash(states: StateUpdates) -> Felt { + let replaced_classes_len = states.replaced_classes.len(); + let deployed_contracts_len = states.deployed_contracts.len(); + let updated_contracts_len = Felt::from(deployed_contracts_len + replaced_classes_len); + // flatten the updated contracts into a single list of Felt values + let updated_contracts = states.deployed_contracts.into_iter().chain(states.replaced_classes); + let updated_contracts = updated_contracts.flat_map(|(addr, hash)| vec![addr.into(), hash]); + + let declared_classes = states.declared_classes; + let declared_classes_len = Felt::from(declared_classes.len()); + let declared_classes = declared_classes.into_iter().flat_map(|e| vec![e.0, e.1]); + + let deprecated_declared_classes = states.deprecated_declared_classes; + let deprecated_declared_classes_len = Felt::from(deprecated_declared_classes.len()); + + let storage_updates = states.storage_updates; + let storage_updates_len = Felt::from(storage_updates.len()); + let storage_updates = storage_updates.into_iter().flat_map(|update| { + let address = Felt::from(update.0); + let storage_entries_len = Felt::from(update.1.len()); + let storage_entries = update.1.into_iter().flat_map(|entries| vec![entries.0, entries.1]); + iter::once(address).chain(iter::once(storage_entries_len)).chain(storage_entries) + }); + + let nonce_updates = states.nonce_updates; + let nonces_len = Felt::from(nonce_updates.len()); + let nonce_updates = nonce_updates.into_iter().flat_map(|nonce| vec![nonce.0.into(), nonce.1]); + + let magic = short_string!("STARKNET_STATE_DIFF0"); + let elements: Vec = iter::once(magic) + .chain(iter::once(updated_contracts_len)) + .chain(updated_contracts) + .chain(iter::once(declared_classes_len)) + .chain(declared_classes) + .chain(iter::once(deprecated_declared_classes_len)) + .chain(deprecated_declared_classes) + .chain(iter::once(Felt::ONE)) + .chain(iter::once(Felt::ZERO)) + .chain(iter::once(storage_updates_len)) + .chain(storage_updates) + .chain(iter::once(nonces_len)) + .chain(nonce_updates) + .collect(); + + hash::Poseidon::hash_array(&elements) +} diff --git a/crates/katana/storage/db/Cargo.toml b/crates/katana/storage/db/Cargo.toml index 36b86bd266..b92f75b51a 100644 --- a/crates/katana/storage/db/Cargo.toml +++ b/crates/katana/storage/db/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true [dependencies] katana-primitives = { workspace = true, features = [ "arbitrary" ] } +katana-trie.workspace = true anyhow.workspace = true dojo-metrics.workspace = true @@ -17,12 +18,16 @@ parking_lot.workspace = true roaring = { version = "0.10.3", features = [ "serde" ] } serde.workspace = true serde_json.workspace = true +starknet.workspace = true +starknet-types-core.workspace = true tempfile.workspace = true thiserror.workspace = true tracing.workspace = true # codecs +bitvec.workspace = true postcard = { workspace = true, optional = true } +smallvec = "1.13.2" [dependencies.libmdbx] git = "https://github.com/paradigmxyz/reth.git" @@ -32,7 +37,6 @@ rev = "b34b0d3" [dev-dependencies] arbitrary.workspace = true criterion.workspace = true -starknet.workspace = true [features] default = [ "postcard" ] diff --git a/crates/katana/storage/db/src/codecs/postcard.rs b/crates/katana/storage/db/src/codecs/postcard.rs index 7acf83bead..2a154fb756 100644 --- a/crates/katana/storage/db/src/codecs/postcard.rs +++ b/crates/katana/storage/db/src/codecs/postcard.rs @@ -11,6 +11,7 @@ use crate::error::CodecError; use crate::models::block::StoredBlockBodyIndices; use crate::models::contract::ContractInfoChangeList; use crate::models::list::BlockList; +use crate::models::trie::TrieDatabaseValue; macro_rules! impl_compress_and_decompress_for_table_values { ($($name:ty),*) => { @@ -38,6 +39,7 @@ impl_compress_and_decompress_for_table_values!( Header, Receipt, Felt, + TrieDatabaseValue, ContractAddress, BlockList, GenericContractInfo, diff --git a/crates/katana/storage/db/src/lib.rs b/crates/katana/storage/db/src/lib.rs index 8f9179d40a..7b72db2669 100644 --- a/crates/katana/storage/db/src/lib.rs +++ b/crates/katana/storage/db/src/lib.rs @@ -13,6 +13,7 @@ pub mod error; pub mod mdbx; pub mod models; pub mod tables; +pub mod trie; pub mod utils; pub mod version; diff --git a/crates/katana/storage/db/src/models/mod.rs b/crates/katana/storage/db/src/models/mod.rs index 4279b48986..09fe7d1e0c 100644 --- a/crates/katana/storage/db/src/models/mod.rs +++ b/crates/katana/storage/db/src/models/mod.rs @@ -3,3 +3,4 @@ pub mod class; pub mod contract; pub mod list; pub mod storage; +pub mod trie; diff --git a/crates/katana/storage/db/src/models/trie.rs b/crates/katana/storage/db/src/models/trie.rs new file mode 100644 index 0000000000..b10456af57 --- /dev/null +++ b/crates/katana/storage/db/src/models/trie.rs @@ -0,0 +1,81 @@ +use katana_trie::bonsai::ByteVec; +use serde::{Deserialize, Serialize}; + +use crate::codecs::{Decode, Encode}; +use crate::error::CodecError; + +#[repr(u8)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +pub enum TrieDatabaseKeyType { + Trie = 0, + Flat, + TrieLog, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TrieDatabaseKey { + pub r#type: TrieDatabaseKeyType, + pub key: Vec, +} + +pub type TrieDatabaseValue = ByteVec; + +impl Encode for TrieDatabaseKey { + type Encoded = Vec; + + fn encode(self) -> Self::Encoded { + let mut encoded = Vec::new(); + encoded.push(self.r#type as u8); + encoded.extend(self.key); + encoded + } +} + +impl Decode for TrieDatabaseKey { + fn decode>(bytes: B) -> Result { + let bytes = bytes.as_ref(); + if bytes.is_empty() { + panic!("emptyy buffer") + } + + let r#type = match bytes[0] { + 0 => TrieDatabaseKeyType::Trie, + 1 => TrieDatabaseKeyType::Flat, + 2 => TrieDatabaseKeyType::TrieLog, + _ => panic!("Invalid trie database key type"), + }; + + let key = bytes[1..].to_vec(); + + Ok(TrieDatabaseKey { r#type, key }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_trie_key_roundtrip() { + let key = TrieDatabaseKey { r#type: TrieDatabaseKeyType::Trie, key: vec![1, 2, 3] }; + let encoded = key.clone().encode(); + let decoded = TrieDatabaseKey::decode(encoded).unwrap(); + assert_eq!(key, decoded); + } + + #[test] + fn test_flat_key_roundtrip() { + let key = TrieDatabaseKey { r#type: TrieDatabaseKeyType::Flat, key: vec![4, 5, 6] }; + let encoded = key.clone().encode(); + let decoded = TrieDatabaseKey::decode(encoded).unwrap(); + assert_eq!(key, decoded); + } + + #[test] + fn test_trielog_key_roundtrip() { + let key = TrieDatabaseKey { r#type: TrieDatabaseKeyType::TrieLog, key: vec![7, 8, 9] }; + let encoded = key.clone().encode(); + let decoded = TrieDatabaseKey::decode(encoded).unwrap(); + assert_eq!(key, decoded); + } +} diff --git a/crates/katana/storage/db/src/tables.rs b/crates/katana/storage/db/src/tables.rs index ccb02bd147..3ebfc050d0 100644 --- a/crates/katana/storage/db/src/tables.rs +++ b/crates/katana/storage/db/src/tables.rs @@ -10,6 +10,7 @@ use crate::models::block::StoredBlockBodyIndices; use crate::models::contract::{ContractClassChange, ContractInfoChangeList, ContractNonceChange}; use crate::models::list::BlockList; use crate::models::storage::{ContractStorageEntry, ContractStorageKey, StorageEntry}; +use crate::models::trie::{TrieDatabaseKey, TrieDatabaseValue}; pub trait Key: Encode + Decode + Clone + std::fmt::Debug {} pub trait Value: Compress + Decompress + std::fmt::Debug {} @@ -35,6 +36,8 @@ pub trait DupSort: Table { type SubKey: Key; } +pub trait Trie: Table {} + /// Enum for the types of tables present in libmdbx. #[derive(Debug, PartialEq, Copy, Clone)] pub enum TableType { @@ -44,7 +47,7 @@ pub enum TableType { DupSort, } -pub const NUM_TABLES: usize = 23; +pub const NUM_TABLES: usize = 26; /// Macro to declare `libmdbx` tables. #[macro_export] @@ -167,7 +170,10 @@ define_tables_enum! {[ (NonceChangeHistory, TableType::DupSort), (ClassChangeHistory, TableType::DupSort), (StorageChangeHistory, TableType::DupSort), - (StorageChangeSet, TableType::Table) + (StorageChangeSet, TableType::Table), + (ClassTrie, TableType::Table), + (ContractTrie, TableType::Table), + (ContractStorageTrie, TableType::Table) ]} tables! { @@ -219,14 +225,23 @@ tables! { NonceChangeHistory: (BlockNumber, ContractAddress) => ContractNonceChange, /// Contract class hash changes by block. ClassChangeHistory: (BlockNumber, ContractAddress) => ContractClassChange, - /// storage change set StorageChangeSet: (ContractStorageKey) => BlockList, /// Account storage change set - StorageChangeHistory: (BlockNumber, ContractStorageKey) => ContractStorageEntry - + StorageChangeHistory: (BlockNumber, ContractStorageKey) => ContractStorageEntry, + + /// Class trie + ClassTrie: (TrieDatabaseKey) => TrieDatabaseValue, + /// Contract trie + ContractTrie: (TrieDatabaseKey) => TrieDatabaseValue, + /// Contract storage trie + ContractStorageTrie: (TrieDatabaseKey) => TrieDatabaseValue } +impl Trie for ClassTrie {} +impl Trie for ContractTrie {} +impl Trie for ContractStorageTrie {} + #[cfg(test)] mod tests { @@ -258,6 +273,8 @@ mod tests { assert_eq!(Tables::ALL[20].name(), ClassChangeHistory::NAME); assert_eq!(Tables::ALL[21].name(), StorageChangeHistory::NAME); assert_eq!(Tables::ALL[22].name(), StorageChangeSet::NAME); + assert_eq!(Tables::ALL[23].name(), ClassTrie::NAME); + assert_eq!(Tables::ALL[24].name(), ContractTrie::NAME); assert_eq!(Tables::Headers.table_type(), TableType::Table); assert_eq!(Tables::BlockHashes.table_type(), TableType::Table); @@ -282,6 +299,8 @@ mod tests { assert_eq!(Tables::ClassChangeHistory.table_type(), TableType::DupSort); assert_eq!(Tables::StorageChangeHistory.table_type(), TableType::DupSort); assert_eq!(Tables::StorageChangeSet.table_type(), TableType::Table); + assert_eq!(Tables::ClassTrie.table_type(), TableType::Table); + assert_eq!(Tables::ContractTrie.table_type(), TableType::Table); } use katana_primitives::address; diff --git a/crates/katana/storage/db/src/trie/class.rs b/crates/katana/storage/db/src/trie/class.rs new file mode 100644 index 0000000000..4e853829e9 --- /dev/null +++ b/crates/katana/storage/db/src/trie/class.rs @@ -0,0 +1,55 @@ +use bitvec::order::Msb0; +use bitvec::vec::BitVec; +use bitvec::view::AsBits; +use katana_primitives::block::BlockNumber; +use katana_primitives::class::{ClassHash, CompiledClassHash}; +use katana_primitives::Felt; +use katana_trie::bonsai::id::BasicId; +use katana_trie::bonsai::{BonsaiStorage, BonsaiStorageConfig}; +use starknet::macros::short_string; +use starknet_types_core::hash::{Poseidon, StarkHash}; + +use crate::abstraction::DbTxMut; +use crate::tables; +use crate::trie::TrieDb; + +// https://docs.starknet.io/architecture-and-concepts/network-architecture/starknet-state/#classes_trie +const CONTRACT_CLASS_LEAF_V0: Felt = short_string!("CONTRACT_CLASS_LEAF_V0"); + +#[derive(Debug)] +pub struct ClassTrie { + inner: BonsaiStorage, Poseidon>, +} + +impl ClassTrie { + pub fn new(tx: Tx) -> Self { + let config = BonsaiStorageConfig { + max_saved_trie_logs: Some(0), + max_saved_snapshots: Some(0), + snapshot_interval: u64::MAX, + }; + + let db = TrieDb::::new(tx); + let inner = BonsaiStorage::new(db, config).unwrap(); + + Self { inner } + } + + pub fn insert(&mut self, hash: ClassHash, compiled_hash: CompiledClassHash) { + let value = Poseidon::hash(&CONTRACT_CLASS_LEAF_V0, &compiled_hash); + let key: BitVec = hash.to_bytes_be().as_bits()[5..].to_owned(); + self.inner.insert(self.bonsai_identifier(), &key, &value).unwrap(); + } + + pub fn commit(&mut self, block_number: BlockNumber) { + self.inner.commit(BasicId::new(block_number)).unwrap(); + } + + pub fn root(&self) -> Felt { + self.inner.root_hash(self.bonsai_identifier()).unwrap() + } + + fn bonsai_identifier(&self) -> &'static [u8] { + b"1" + } +} diff --git a/crates/katana/storage/db/src/trie/contract.rs b/crates/katana/storage/db/src/trie/contract.rs new file mode 100644 index 0000000000..5e460739aa --- /dev/null +++ b/crates/katana/storage/db/src/trie/contract.rs @@ -0,0 +1,83 @@ +use bitvec::order::Msb0; +use bitvec::vec::BitVec; +use bitvec::view::AsBits; +use katana_primitives::block::BlockNumber; +use katana_primitives::contract::{StorageKey, StorageValue}; +use katana_primitives::{ContractAddress, Felt}; +use katana_trie::bonsai::id::BasicId; +use katana_trie::bonsai::{BonsaiStorage, BonsaiStorageConfig}; +use starknet_types_core::hash::Poseidon; + +use crate::abstraction::DbTxMut; +use crate::tables; +use crate::trie::TrieDb; + +#[derive(Debug)] +pub struct StorageTrie { + inner: BonsaiStorage, Poseidon>, +} + +impl StorageTrie { + pub fn new(tx: Tx) -> Self { + let config = BonsaiStorageConfig { + max_saved_trie_logs: Some(0), + max_saved_snapshots: Some(0), + snapshot_interval: u64::MAX, + }; + + let db = TrieDb::::new(tx); + let inner = BonsaiStorage::new(db, config).unwrap(); + + Self { inner } + } + + pub fn insert(&mut self, address: ContractAddress, key: StorageKey, value: StorageValue) { + let key: BitVec = key.to_bytes_be().as_bits()[5..].to_owned(); + self.inner.insert(&address.to_bytes_be(), &key, &value).unwrap(); + } + + pub fn commit(&mut self, block_number: BlockNumber) { + self.inner.commit(BasicId::new(block_number)).unwrap(); + } + + pub fn root(&self, address: &ContractAddress) -> Felt { + self.inner.root_hash(&address.to_bytes_be()).unwrap() + } +} + +#[derive(Debug)] +pub struct ContractTrie { + inner: BonsaiStorage, Poseidon>, +} + +impl ContractTrie { + pub fn new(tx: Tx) -> Self { + let config = BonsaiStorageConfig { + max_saved_trie_logs: Some(0), + max_saved_snapshots: Some(0), + snapshot_interval: u64::MAX, + }; + + let db = TrieDb::::new(tx); + let inner = BonsaiStorage::new(db, config).unwrap(); + + Self { inner } + } + + pub fn insert(&mut self, address: ContractAddress, state_hash: Felt) { + let key: BitVec = address.to_bytes_be().as_bits()[5..].to_owned(); + self.inner.insert(self.bonsai_identifier(), &key, &state_hash).unwrap(); + } + + pub fn commit(&mut self, block_number: BlockNumber) { + self.inner.commit(BasicId::new(block_number)).unwrap(); + } + + pub fn root(&self) -> Felt { + self.inner.root_hash(self.bonsai_identifier()).unwrap() + } + + fn bonsai_identifier(&self) -> &'static [u8] { + b"1" + } +} diff --git a/crates/katana/storage/db/src/trie/mod.rs b/crates/katana/storage/db/src/trie/mod.rs new file mode 100644 index 0000000000..823c9f28ba --- /dev/null +++ b/crates/katana/storage/db/src/trie/mod.rs @@ -0,0 +1,150 @@ +use std::marker::PhantomData; + +use anyhow::Result; +use katana_trie::bonsai::id::BasicId; +use katana_trie::bonsai::{self, ByteVec, DatabaseKey}; +use smallvec::ToSmallVec; + +use crate::abstraction::{DbCursor, DbTxMut}; +use crate::models::trie::{TrieDatabaseKey, TrieDatabaseKeyType}; +use crate::models::{self}; +use crate::tables; + +mod class; +mod contract; + +pub use class::ClassTrie; +pub use contract::{ContractTrie, StorageTrie}; + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(#[from] crate::error::DatabaseError); + +impl katana_trie::bonsai::DBError for Error {} + +#[derive(Debug)] +pub struct TrieDb { + tx: Tx, + _table: PhantomData, +} + +impl TrieDb +where + Tb: tables::Trie, + Tx: DbTxMut, +{ + pub fn new(tx: Tx) -> Self { + Self { tx, _table: PhantomData } + } +} + +impl bonsai::BonsaiDatabase for TrieDb +where + Tb: tables::Trie, + Tx: DbTxMut, +{ + type Batch = (); + type DatabaseError = Error; + + fn create_batch(&self) -> Self::Batch {} + + fn remove_by_prefix(&mut self, prefix: &DatabaseKey<'_>) -> Result<(), Self::DatabaseError> { + let mut cursor = self.tx.cursor_mut::()?; + let walker = cursor.walk(None)?; + + let mut keys_to_remove = Vec::new(); + // iterate over all entries in the table + for entry in walker { + let (key, _) = entry?; + if key.key.starts_with(prefix.as_slice()) { + keys_to_remove.push(key); + } + } + + for key in keys_to_remove { + let _ = self.tx.delete::(key, None)?; + } + + Ok(()) + } + + fn get(&self, key: &DatabaseKey<'_>) -> Result, Self::DatabaseError> { + let value = self.tx.get::(to_db_key(key))?; + Ok(value) + } + + fn get_by_prefix( + &self, + prefix: &DatabaseKey<'_>, + ) -> Result, Self::DatabaseError> { + let _ = prefix; + todo!() + } + + fn insert( + &mut self, + key: &DatabaseKey<'_>, + value: &[u8], + _batch: Option<&mut Self::Batch>, + ) -> Result, Self::DatabaseError> { + let key = to_db_key(key); + let value: ByteVec = value.to_smallvec(); + let old_value = self.tx.get::(key.clone())?; + self.tx.put::(key, value)?; + Ok(old_value) + } + + fn remove( + &mut self, + key: &DatabaseKey<'_>, + _batch: Option<&mut Self::Batch>, + ) -> Result, Self::DatabaseError> { + let key = to_db_key(key); + let old_value = self.tx.get::(key.clone())?; + self.tx.delete::(key, None)?; + Ok(old_value) + } + + fn contains(&self, key: &DatabaseKey<'_>) -> Result { + let key = to_db_key(key); + let value = self.tx.get::(key)?; + Ok(value.is_some()) + } + + fn write_batch(&mut self, _batch: Self::Batch) -> Result<(), Self::DatabaseError> { + Ok(()) + } +} + +impl bonsai::BonsaiPersistentDatabase for TrieDb +where + Tb: tables::Trie, + Tx: DbTxMut, +{ + type DatabaseError = Error; + type Transaction = TrieDb; + + fn snapshot(&mut self, _: BasicId) {} + + fn merge(&mut self, _: Self::Transaction) -> Result<(), Self::DatabaseError> { + todo!(); + } + + fn transaction(&self, _: BasicId) -> Option { + todo!(); + } +} + +fn to_db_key(key: &DatabaseKey<'_>) -> models::trie::TrieDatabaseKey { + match key { + DatabaseKey::Flat(bytes) => { + TrieDatabaseKey { key: bytes.to_vec(), r#type: TrieDatabaseKeyType::Flat } + } + DatabaseKey::Trie(bytes) => { + TrieDatabaseKey { key: bytes.to_vec(), r#type: TrieDatabaseKeyType::Trie } + } + DatabaseKey::TrieLog(bytes) => { + TrieDatabaseKey { key: bytes.to_vec(), r#type: TrieDatabaseKeyType::TrieLog } + } + } +} diff --git a/crates/katana/storage/provider/Cargo.toml b/crates/katana/storage/provider/Cargo.toml index 4cf24e81b8..fa7860cee3 100644 --- a/crates/katana/storage/provider/Cargo.toml +++ b/crates/katana/storage/provider/Cargo.toml @@ -9,6 +9,7 @@ version.workspace = true [dependencies] katana-db = { workspace = true, features = [ "test-utils" ] } katana-primitives = { workspace = true, features = [ "rpc" ] } +katana-trie.workspace = true anyhow.workspace = true auto_impl.workspace = true @@ -16,6 +17,9 @@ parking_lot.workspace = true thiserror.workspace = true tracing.workspace = true +bitvec.workspace = true +starknet-types-core.workspace = true + # fork provider deps futures = { workspace = true, optional = true } starknet = { workspace = true, optional = true } diff --git a/crates/katana/storage/provider/src/lib.rs b/crates/katana/storage/provider/src/lib.rs index b7c1f7b430..cc37ce473e 100644 --- a/crates/katana/storage/provider/src/lib.rs +++ b/crates/katana/storage/provider/src/lib.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::ops::{Range, RangeInclusive}; use katana_db::models::block::StoredBlockBodyIndices; @@ -18,6 +19,7 @@ use traits::contract::{ContractClassProvider, ContractClassWriter}; use traits::env::BlockEnvProvider; use traits::state::{StateRootProvider, StateWriter}; use traits::transaction::{TransactionStatusProvider, TransactionTraceProvider}; +use traits::trie::{ClassTrieWriter, ContractTrieWriter}; pub mod error; pub mod providers; @@ -380,3 +382,29 @@ where self.provider.block_env_at(id) } } + +impl ClassTrieWriter for BlockchainProvider +where + Db: ClassTrieWriter, +{ + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> ProviderResult { + self.provider.insert_updates(block_number, updates) + } +} + +impl ContractTrieWriter for BlockchainProvider +where + Db: ContractTrieWriter, +{ + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> ProviderResult { + self.provider.insert_updates(block_number, state_updates) + } +} diff --git a/crates/katana/storage/provider/src/providers/db/mod.rs b/crates/katana/storage/provider/src/providers/db/mod.rs index 92f6ea1a0f..4d06be8dda 100644 --- a/crates/katana/storage/provider/src/providers/db/mod.rs +++ b/crates/katana/storage/provider/src/providers/db/mod.rs @@ -1,4 +1,5 @@ pub mod state; +pub mod trie; use std::collections::BTreeMap; use std::fmt::Debug; diff --git a/crates/katana/storage/provider/src/providers/db/trie.rs b/crates/katana/storage/provider/src/providers/db/trie.rs new file mode 100644 index 0000000000..65b5b96984 --- /dev/null +++ b/crates/katana/storage/provider/src/providers/db/trie.rs @@ -0,0 +1,117 @@ +use std::collections::{BTreeMap, HashMap}; +use std::fmt::Debug; + +use katana_db::abstraction::Database; +use katana_db::trie; +use katana_db::trie::{ContractTrie, StorageTrie}; +use katana_primitives::block::BlockNumber; +use katana_primitives::class::{ClassHash, CompiledClassHash}; +use katana_primitives::state::StateUpdates; +use katana_primitives::{ContractAddress, Felt}; +use katana_trie::compute_contract_state_hash; + +use crate::providers::db::DbProvider; +use crate::traits::state::{StateFactoryProvider, StateProvider}; +use crate::traits::trie::{ClassTrieWriter, ContractTrieWriter}; + +#[derive(Debug, Default)] +struct ContractLeaf { + pub class_hash: Option, + pub storage_root: Option, + pub nonce: Option, +} + +impl ClassTrieWriter for DbProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> crate::ProviderResult { + let mut trie = trie::ClassTrie::new(self.0.tx_mut()?); + + for (class_hash, compiled_hash) in updates { + trie.insert(*class_hash, *compiled_hash); + } + + trie.commit(block_number); + Ok(trie.root()) + } +} + +impl ContractTrieWriter for DbProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> crate::ProviderResult { + let mut contract_leafs: HashMap = HashMap::new(); + + let leaf_hashes: Vec<_> = { + let mut storage_trie_db = StorageTrie::new(self.0.tx_mut()?); + + // First we insert the contract storage changes + for (address, storage_entries) in &state_updates.storage_updates { + for (key, value) in storage_entries { + storage_trie_db.insert(*address, *key, *value); + } + // insert the contract address in the contract_leafs to put the storage root later + contract_leafs.insert(*address, Default::default()); + } + + // Then we commit them + storage_trie_db.commit(block_number); + + for (address, nonce) in &state_updates.nonce_updates { + contract_leafs.entry(*address).or_default().nonce = Some(*nonce); + } + + for (address, class_hash) in &state_updates.deployed_contracts { + contract_leafs.entry(*address).or_default().class_hash = Some(*class_hash); + } + + for (address, class_hash) in &state_updates.replaced_classes { + contract_leafs.entry(*address).or_default().class_hash = Some(*class_hash); + } + + contract_leafs + .into_iter() + .map(|(address, mut leaf)| { + let storage_root = storage_trie_db.root(&address); + leaf.storage_root = Some(storage_root); + + let latest_state = self.latest().unwrap(); + let leaf_hash = contract_state_leaf_hash(latest_state, &address, &leaf); + + (address, leaf_hash) + }) + .collect::>() + }; + + let mut contract_trie_db = ContractTrie::new(self.0.tx_mut()?); + + for (k, v) in leaf_hashes { + contract_trie_db.insert(k, v); + } + + contract_trie_db.commit(block_number); + Ok(contract_trie_db.root()) + } +} + +// computes the contract state leaf hash +fn contract_state_leaf_hash( + provider: impl StateProvider, + address: &ContractAddress, + contract_leaf: &ContractLeaf, +) -> Felt { + let nonce = + contract_leaf.nonce.unwrap_or(provider.nonce(*address).unwrap().unwrap_or_default()); + + let class_hash = contract_leaf + .class_hash + .unwrap_or(provider.class_hash_of_contract(*address).unwrap().unwrap_or_default()); + + let storage_root = contract_leaf.storage_root.expect("root need to set"); + + compute_contract_state_hash(&class_hash, &storage_root, &nonce) +} diff --git a/crates/katana/storage/provider/src/providers/fork/mod.rs b/crates/katana/storage/provider/src/providers/fork/mod.rs index 5383d3c960..115c6dd45a 100644 --- a/crates/katana/storage/provider/src/providers/fork/mod.rs +++ b/crates/katana/storage/provider/src/providers/fork/mod.rs @@ -1,6 +1,7 @@ pub mod backend; pub mod state; +use std::collections::BTreeMap; use std::ops::{Range, RangeInclusive}; use std::sync::Arc; @@ -16,6 +17,7 @@ use katana_primitives::receipt::Receipt; use katana_primitives::state::{StateUpdates, StateUpdatesWithDeclaredClasses}; use katana_primitives::trace::TxExecInfo; use katana_primitives::transaction::{Tx, TxHash, TxNumber, TxWithHash}; +use katana_primitives::Felt; use parking_lot::RwLock; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; @@ -36,6 +38,7 @@ use crate::traits::transaction::{ ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionTraceProvider, TransactionsProviderExt, }; +use crate::traits::trie::{ClassTrieWriter, ContractTrieWriter}; use crate::ProviderResult; #[derive(Debug)] @@ -582,3 +585,27 @@ impl BlockEnvProvider for ForkedProvider { })) } } + +impl ClassTrieWriter for ForkedProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> ProviderResult { + let _ = block_number; + let _ = updates; + Ok(Felt::ZERO) + } +} + +impl ContractTrieWriter for ForkedProvider { + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> ProviderResult { + let _ = block_number; + let _ = state_updates; + Ok(Felt::ZERO) + } +} diff --git a/crates/katana/storage/provider/src/traits/mod.rs b/crates/katana/storage/provider/src/traits/mod.rs index 762de20465..1fdbcf3de2 100644 --- a/crates/katana/storage/provider/src/traits/mod.rs +++ b/crates/katana/storage/provider/src/traits/mod.rs @@ -4,3 +4,4 @@ pub mod env; pub mod state; pub mod state_update; pub mod transaction; +pub mod trie; diff --git a/crates/katana/storage/provider/src/traits/trie.rs b/crates/katana/storage/provider/src/traits/trie.rs new file mode 100644 index 0000000000..8570a30b88 --- /dev/null +++ b/crates/katana/storage/provider/src/traits/trie.rs @@ -0,0 +1,26 @@ +use std::collections::BTreeMap; + +use katana_primitives::block::BlockNumber; +use katana_primitives::class::{ClassHash, CompiledClassHash}; +use katana_primitives::state::StateUpdates; +use katana_primitives::Felt; + +use crate::ProviderResult; + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait ClassTrieWriter: Send + Sync { + fn insert_updates( + &self, + block_number: BlockNumber, + updates: &BTreeMap, + ) -> ProviderResult; +} + +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait ContractTrieWriter: Send + Sync { + fn insert_updates( + &self, + block_number: BlockNumber, + state_updates: &StateUpdates, + ) -> ProviderResult; +} diff --git a/crates/katana/trie/Cargo.toml b/crates/katana/trie/Cargo.toml new file mode 100644 index 0000000000..759dab5347 --- /dev/null +++ b/crates/katana/trie/Cargo.toml @@ -0,0 +1,23 @@ +[package] +edition.workspace = true +license.workspace = true +name = "katana-trie" +repository.workspace = true +version.workspace = true + +[dependencies] +katana-primitives.workspace = true + +anyhow.workspace = true +bitvec.workspace = true +serde.workspace = true +slab = "0.4.9" +starknet.workspace = true +starknet-types-core.workspace = true +thiserror.workspace = true + +[dependencies.bonsai-trie] +default-features = false +features = [ "std" ] +git = "https://github.com/madara-alliance/bonsai-trie/" +rev = "56d7d62" diff --git a/crates/katana/trie/src/lib.rs b/crates/katana/trie/src/lib.rs new file mode 100644 index 0000000000..692b328e2a --- /dev/null +++ b/crates/katana/trie/src/lib.rs @@ -0,0 +1,88 @@ +use anyhow::Result; +use bitvec::vec::BitVec; +pub use bonsai_trie as bonsai; +use bonsai_trie::id::BasicId; +use bonsai_trie::{BonsaiDatabase, BonsaiPersistentDatabase}; +use katana_primitives::class::ClassHash; +use katana_primitives::Felt; +use starknet_types_core::hash::{Pedersen, StarkHash}; + +/// A helper trait to define a database that can be used as a Bonsai Trie. +/// +/// Basically a short hand for `BonsaiDatabase + BonsaiPersistentDatabase`. +pub trait BonsaiTrieDb: BonsaiDatabase + BonsaiPersistentDatabase {} +impl BonsaiTrieDb for T where T: BonsaiDatabase + BonsaiPersistentDatabase {} + +pub fn compute_merkle_root(values: &[Felt]) -> Result +where + H: StarkHash + Send + Sync, +{ + use bonsai_trie::id::BasicId; + use bonsai_trie::{databases, BonsaiStorage, BonsaiStorageConfig}; + + // the value is irrelevant + const IDENTIFIER: &[u8] = b"1"; + + let config = BonsaiStorageConfig::default(); + let bonsai_db = databases::HashMapDb::::default(); + let mut bs = BonsaiStorage::<_, _, H>::new(bonsai_db, config).unwrap(); + + for (id, value) in values.iter().enumerate() { + let key = BitVec::from_iter(id.to_be_bytes()); + bs.insert(IDENTIFIER, key.as_bitslice(), value).unwrap(); + } + + let id = bonsai_trie::id::BasicIdBuilder::new().new_id(); + bs.commit(id).unwrap(); + + Ok(bs.root_hash(IDENTIFIER).unwrap()) +} + +// H(H(H(class_hash, storage_root), nonce), 0), where H is the pedersen hash +pub fn compute_contract_state_hash( + class_hash: &ClassHash, + storage_root: &Felt, + nonce: &Felt, +) -> Felt { + const CONTRACT_STATE_HASH_VERSION: Felt = Felt::ZERO; + let hash = Pedersen::hash(class_hash, storage_root); + let hash = Pedersen::hash(&hash, nonce); + Pedersen::hash(&hash, &CONTRACT_STATE_HASH_VERSION) +} + +#[cfg(test)] +mod tests { + + use katana_primitives::contract::Nonce; + use katana_primitives::felt; + use starknet_types_core::hash; + + use super::*; + + // Taken from Pathfinder: https://github.com/eqlabs/pathfinder/blob/29f93d0d6ad8758fdcf5ae3a8bd2faad2a3bc92b/crates/merkle-tree/src/transaction.rs#L70-L88 + #[test] + fn test_commitment_merkle_tree() { + let hashes = vec![Felt::from(1), Felt::from(2), Felt::from(3), Felt::from(4)]; + + // Produced by the cairo-lang Python implementation: + // `hex(asyncio.run(calculate_patricia_root([1, 2, 3, 4], height=64, ffc=ffc))))` + let expected_root_hash = + felt!("0x1a0e579b6b444769e4626331230b5ae39bd880f47e703b73fa56bf77e52e461"); + let computed_root_hash = compute_merkle_root::(&hashes).unwrap(); + + assert_eq!(expected_root_hash, computed_root_hash); + } + + // Taken from Pathfinder: https://github.com/eqlabs/pathfinder/blob/29f93d0d6ad8758fdcf5ae3a8bd2faad2a3bc92b/crates/merkle-tree/src/contract_state.rs#L236C5-L252C6 + #[test] + fn hash() { + let root = felt!("0x4fb440e8ca9b74fc12a22ebffe0bc0658206337897226117b985434c239c028"); + let class_hash = felt!("0x2ff4903e17f87b298ded00c44bfeb22874c5f73be2ced8f1d9d9556fb509779"); + let nonce = Nonce::ZERO; + + let result = compute_contract_state_hash(&class_hash, &root, &nonce); + let expected = felt!("0x7161b591c893836263a64f2a7e0d829c92f6956148a60ce5e99a3f55c7973f3"); + + assert_eq!(result, expected); + } +} diff --git a/crates/saya/provider/src/rpc/mod.rs b/crates/saya/provider/src/rpc/mod.rs index 53342ed35a..319bf2df67 100644 --- a/crates/saya/provider/src/rpc/mod.rs +++ b/crates/saya/provider/src/rpc/mod.rs @@ -90,6 +90,7 @@ impl Provider for JsonRpcProvider { hash: block.block_hash, header: Header { events_count: 0, + state_diff_length: 0, transaction_count: 0, events_commitment: Felt::ZERO, receipts_commitment: Felt::ZERO, diff --git a/examples/spawn-and-move/manifest_dev.json b/examples/spawn-and-move/manifest_dev.json index 20ae62e96d..694207eade 100644 --- a/examples/spawn-and-move/manifest_dev.json +++ b/examples/spawn-and-move/manifest_dev.json @@ -3,12 +3,1126 @@ "class_hash": "0x139239a99d627697b19b9856beaef7896fc75375caf3d750dd76982a7afeb78", "address": "0x234d358c2ec21c98a229966bd2bae6dbf2c517969c361bc649361f9055afc32", "seed": "dojo_examples", - "name": "example" + "name": "example", + "abi": [ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::iworld::IWorld" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "enum", + "name": "dojo::world::resource::Resource", + "variants": [ + { + "name": "Model", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Event", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Contract", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "World", + "type": "()" + }, + { + "name": "Unregistered", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "dojo::model::metadata::ResourceMetadata", + "members": [ + { + "name": "resource_id", + "type": "core::felt252" + }, + { + "name": "metadata_uri", + "type": "core::byte_array::ByteArray" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "enum", + "name": "dojo::model::definition::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "dojo::meta::layout::FieldLayout", + "members": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "dojo::meta::layout::Layout", + "variants": [ + { + "name": "Fixed", + "type": "core::array::Span::" + }, + { + "name": "Struct", + "type": "core::array::Span::" + }, + { + "name": "Tuple", + "type": "core::array::Span::" + }, + { + "name": "Array", + "type": "core::array::Span::" + }, + { + "name": "ByteArray", + "type": "()" + }, + { + "name": "Enum", + "type": "core::array::Span::" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::iworld::IWorld", + "items": [ + { + "type": "function", + "name": "resource", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::world::resource::Resource" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "metadata", + "inputs": [ + { + "name": "resource_selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_metadata", + "inputs": [ + { + "name": "metadata", + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_namespace", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_contract", + "inputs": [ + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "init_contract", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "init_calldata", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_contract", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit_event", + "inputs": [ + { + "name": "event_selector", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "historical", + "type": "core::bool" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::iworld::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::iworld::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "world_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "struct", + "members": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "hash", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "salt", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractInitialized", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "init_calldata", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventEmitted", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "system_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "historical", + "type": "core::bool", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "uri", + "type": "core::byte_array::ByteArray", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "member_selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WriterUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world_contract::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "nested" + }, + { + "name": "NamespaceRegistered", + "type": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world_contract::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "EventRegistered", + "type": "dojo::world::world_contract::world::EventRegistered", + "kind": "nested" + }, + { + "name": "ContractRegistered", + "type": "dojo::world::world_contract::world::ContractRegistered", + "kind": "nested" + }, + { + "name": "ModelUpgraded", + "type": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "nested" + }, + { + "name": "EventUpgraded", + "type": "dojo::world::world_contract::world::EventUpgraded", + "kind": "nested" + }, + { + "name": "ContractUpgraded", + "type": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "nested" + }, + { + "name": "ContractInitialized", + "type": "dojo::world::world_contract::world::ContractInitialized", + "kind": "nested" + }, + { + "name": "EventEmitted", + "type": "dojo::world::world_contract::world::EventEmitted", + "kind": "nested" + }, + { + "name": "MetadataUpdate", + "type": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateMember", + "type": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "nested" + }, + { + "name": "WriterUpdated", + "type": "dojo::world::world_contract::world::WriterUpdated", + "kind": "nested" + }, + { + "name": "OwnerUpdated", + "type": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "nested" + } + ] + } + ] }, "contracts": [ { - "address": "0x5a24b6dbf1b4b07f26f9920f490bb6a546f4620cb53d8a98b1da6317c3b8451", - "class_hash": "0x7b375686817add5ce9bef07ac7e4366fdd39d2be910f79896974ffda2471664", + "address": "0x7bc340927668bc87eea10d95cb2dfe0fa10be12075fe8189f363643205c34d4", + "class_hash": "0x40fc071ee4de6ed9a5a3fe813d12d898f3b58d9397bf5247c9e4d3274fac5f", "abi": [ { "type": "impl", @@ -324,7 +1438,7 @@ } ], "init_calldata": [], - "tag": "actions", + "tag": "ns-actions", "systems": [] }, { @@ -505,12 +1619,12 @@ } ], "init_calldata": [], - "tag": "dungeon", + "tag": "ns-dungeon", "systems": [] }, { - "address": "0x7e8f3994bc030bea8d1072fcb4d37bb0f1bdc0d8ff5bf3f7bd5211993c42736", - "class_hash": "0x10f24f231c572fa028b886c626e274856de5b7f4988f60dc442b691da8460a4", + "address": "0x41ceb76687e3653610fffc3c830607d90079e9c5d96cfb6f270c8231e9ee9db", + "class_hash": "0xb429efa8844a23048e81db941adf6fc5776dab4ee0c06c6c964048a3f88192", "abi": [ { "type": "impl", @@ -668,12 +1782,12 @@ } ], "init_calldata": [], - "tag": "mock_token", + "tag": "ns-mock_token", "systems": [] }, { - "address": "0x22dd2a3e90b337d147a7107e52dce4880f7efb85a93c8b5d9ca305ab978ec34", - "class_hash": "0x7da188de97bc0e2a08c20d3c75428ed2173bb0282cafd6ba693bc09f9d528c8", + "address": "0x79e0653fbebdbdb864ca69d1470b263f2efdfce9cf355cfe9c7719627eff792", + "class_hash": "0x3356ff99134a997be6f9366c47ad18993aac3e29803819cc80309a57fa23f88", "abi": [ { "type": "impl", @@ -838,7 +1952,7 @@ "init_calldata": [ "0xff" ], - "tag": "others", + "tag": "ns-others", "systems": [] } ], diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index 2a44ce43ae..647fb4db2d 100644 Binary files a/spawn-and-move-db.tar.gz and b/spawn-and-move-db.tar.gz differ diff --git a/types-test-db.tar.gz b/types-test-db.tar.gz index b373ed5947..03f96aaba6 100644 Binary files a/types-test-db.tar.gz and b/types-test-db.tar.gz differ