From df51270ef0a1303bccca729d5051537e154f95ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20Bezerra?= Date: Mon, 25 Nov 2024 14:49:02 -0300 Subject: [PATCH 1/4] refac: simplify storage interface --- src/eth/storage/mod.rs | 2 +- src/eth/storage/stratus_storage.rs | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/eth/storage/mod.rs b/src/eth/storage/mod.rs index 0566089fb..e53b47964 100644 --- a/src/eth/storage/mod.rs +++ b/src/eth/storage/mod.rs @@ -39,7 +39,7 @@ use crate::eth::primitives::StratusError; use crate::eth::primitives::TransactionExecution; use crate::eth::primitives::TransactionStage; -pub trait Storage: Sized { +pub trait Storage: Send + Sync + 'static { // ------------------------------------------------------------------------- // Block number // ------------------------------------------------------------------------- diff --git a/src/eth/storage/stratus_storage.rs b/src/eth/storage/stratus_storage.rs index 245f509ae..a9f6a86dd 100644 --- a/src/eth/storage/stratus_storage.rs +++ b/src/eth/storage/stratus_storage.rs @@ -57,21 +57,14 @@ impl StratusStorage { #[cfg(test)] pub fn new_test() -> Result { - use super::InMemoryTemporaryStorage; - use crate::eth::storage::InMemoryPermanentStorage; - - let perm = Box::new(InMemoryPermanentStorage::default()); - let temp = Box::new(InMemoryTemporaryStorage::new(0.into())); + let perm = Box::new(super::InMemoryPermanentStorage::default()); + let temp = Box::new(super::InMemoryTemporaryStorage::new(0.into())); Self::new(temp, perm) } } impl Storage for StratusStorage { - // ------------------------------------------------------------------------- - // Block number - // ------------------------------------------------------------------------- - fn read_block_number_to_resume_import(&self) -> Result { #[cfg(feature = "tracing")] let _span = tracing::info_span!("storage::read_block_number_to_resume_import").entered(); From 551d7756f0ec283bdd578cf64f7121802d692e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20Bezerra?= Date: Fri, 29 Nov 2024 18:08:06 -0300 Subject: [PATCH 2/4] fix e2e tests locally --- .../integration/test/leader-follower-miner.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/e2e/cloudwalk-contracts/integration/test/leader-follower-miner.test.ts b/e2e/cloudwalk-contracts/integration/test/leader-follower-miner.test.ts index ddba33004..bc36c9a09 100644 --- a/e2e/cloudwalk-contracts/integration/test/leader-follower-miner.test.ts +++ b/e2e/cloudwalk-contracts/integration/test/leader-follower-miner.test.ts @@ -124,7 +124,9 @@ describe("Miner mode change integration test", function () { updateProviderUrl("stratus"); const response = await sendAndGetFullResponse("stratus_changeMinerMode", ["automine"]); expect(response.data.error.code).eq(-32603); - expect(response.data.error.message).eq("Miner mode change to (automine) is unsupported."); + expect(response.data.error.message.split("\n")[0]).eq( + "Unexpected error: Miner mode change to 'automine' is unsupported.", + ); }); it("Miner change on Leader to External should fail if there are pending transactions", async function () { From dbf44557aacc2a98a871943dc0bfce229eed0d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20Bezerra?= Date: Fri, 29 Nov 2024 18:12:12 -0300 Subject: [PATCH 3/4] refac: reuse `AccountWithSlots` struct for temp storage and cache --- src/eth/storage/mod.rs | 17 +++++++++++++++++ src/eth/storage/temporary/inmemory.rs | 21 +++------------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/eth/storage/mod.rs b/src/eth/storage/mod.rs index e53b47964..56125c681 100644 --- a/src/eth/storage/mod.rs +++ b/src/eth/storage/mod.rs @@ -15,6 +15,7 @@ pub mod permanent; mod stratus_storage; mod temporary; +use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; @@ -95,6 +96,22 @@ pub trait Storage: Send + Sync + 'static { fn translate_to_point_in_time(&self, block_filter: BlockFilter) -> Result; } +#[derive(Debug, Clone)] +pub struct AccountWithSlots { + pub info: Account, + pub slots: HashMap, +} + +impl AccountWithSlots { + /// Creates a new temporary account. + fn new(address: Address) -> Self { + Self { + info: Account::new_empty(address), + slots: HashMap::default(), + } + } +} + // ----------------------------------------------------------------------------- // Config // ----------------------------------------------------------------------------- diff --git a/src/eth/storage/temporary/inmemory.rs b/src/eth/storage/temporary/inmemory.rs index 60030bf8d..e1540aaa7 100644 --- a/src/eth/storage/temporary/inmemory.rs +++ b/src/eth/storage/temporary/inmemory.rs @@ -25,6 +25,7 @@ use crate::eth::primitives::TransactionExecution; use crate::eth::primitives::UnixTime; #[cfg(feature = "dev")] use crate::eth::primitives::UnixTimeNow; +use crate::eth::storage::AccountWithSlots; use crate::eth::storage::TemporaryStorage; /// Number of previous blocks to keep inmemory to detect conflicts between different blocks. @@ -67,7 +68,7 @@ pub struct InMemoryTemporaryStorageState { pub block: PendingBlock, /// Last state of accounts and slots. Can be recreated from the executions inside the pending block. - pub accounts: HashMap, + pub accounts: HashMap, } impl InMemoryTemporaryStorageState { @@ -84,22 +85,6 @@ impl InMemoryTemporaryStorageState { } } -#[derive(Debug, Clone)] -pub struct InMemoryTemporaryAccount { - pub info: Account, - pub slots: HashMap, -} - -impl InMemoryTemporaryAccount { - /// Creates a new temporary account. - fn new(address: Address) -> Self { - Self { - info: Account::new_empty(address), - slots: HashMap::default(), - } - } -} - impl TemporaryStorage for InMemoryTemporaryStorage { // ------------------------------------------------------------------------- // Block number @@ -142,7 +127,7 @@ impl TemporaryStorage for InMemoryTemporaryStorage { .head .accounts .entry(change.address) - .or_insert_with(|| InMemoryTemporaryAccount::new(change.address)); + .or_insert_with(|| AccountWithSlots::new(change.address)); // account basic info if let Some(nonce) = change.nonce.take_ref() { From 697b357f5ac405abd390892bbd4589bc718a81c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20Bezerra?= Date: Fri, 29 Nov 2024 18:14:17 -0300 Subject: [PATCH 4/4] enha: implement slot and account cache which replaces the previous temporary storage linear search with an optimized LRU cache search, this improves TPS and importer-offline speed by around 10% --- Cargo.lock | 47 +++++++-- Cargo.toml | 4 + src/bin/importer_offline.rs | 31 +++--- src/eth/primitives/address.rs | 3 +- src/eth/storage/cache.rs | 89 ++++++++++++++++ src/eth/storage/mod.rs | 2 + src/eth/storage/stratus_storage.rs | 142 ++++++++++++++++---------- src/eth/storage/temporary/inmemory.rs | 2 +- 8 files changed, 238 insertions(+), 82 deletions(-) create mode 100644 src/eth/storage/cache.rs diff --git a/Cargo.lock b/Cargo.lock index 9275440c0..013bacedd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" +checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" dependencies = [ "cc", "glob", @@ -663,15 +663,16 @@ dependencies = [ [[package]] name = "c-kzg" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf100c4cea8f207e883ff91ca886d621d8a166cb04971dfaa9bb8fd99ed95df" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" dependencies = [ "blst", "cc", "glob", "hex", "libc", + "once_cell", "serde", ] @@ -1485,6 +1486,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1794,6 +1801,17 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashlink" version = "0.9.1" @@ -2578,6 +2596,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + [[package]] name = "lz4-sys" version = "1.9.4" @@ -3169,9 +3196,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -4691,9 +4718,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] @@ -5009,6 +5036,7 @@ dependencies = [ "keccak-hasher", "lazy_static", "log", + "lru", "metrics", "metrics-exporter-prometheus", "nanoid", @@ -5020,6 +5048,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", + "parking_lot", "paste", "phf", "phf_codegen", @@ -5033,6 +5062,7 @@ dependencies = [ "rlp", "rocksdb", "rust_decimal", + "rustc-hash 2.0.0", "sasl2-sys", "sentry", "sentry-tracing", @@ -5041,6 +5071,7 @@ dependencies = [ "serde_plain", "serde_urlencoded", "serde_with", + "smallvec", "sqlx", "static_assertions", "stringreader", diff --git a/Cargo.toml b/Cargo.toml index 6988ab37c..31474e329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,15 +26,19 @@ humantime = "=2.1.0" indexmap = { version = "=2.2.6", features = ["serde"] } itertools = "=0.13.0" lazy_static = "=1.4.0" +lru = "=0.12.5" nanoid = "=0.4.0" nonempty = { version = "=0.10.0", features = ["serialize"] } once_cell = "=1.19.0" oneshot = "=0.1.8" +parking_lot = "=0.12.3" paste = "=1.0.15" phf = "=0.11.2" pin-project = "=1.1.5" rand = { version = "=0.8.5", features = ["small_rng"] } rust_decimal = "=1.36.0" +rustc-hash = "=2.0.0" +smallvec = "=1.13.2" static_assertions = "=1.1.0" strum = "=0.26.2" sugars = "=3.0.1" diff --git a/src/bin/importer_offline.rs b/src/bin/importer_offline.rs index f63d418c3..d7c752b09 100644 --- a/src/bin/importer_offline.rs +++ b/src/bin/importer_offline.rs @@ -49,20 +49,19 @@ const RPC_FETCHER_CHANNEL_CAPACITY: usize = 10; /// /// We want to persist to the storage in batches, this means we don't save a /// block right away, but the information from that block still needs to be -/// found in the storage. +/// found in the cache. /// -/// By using a channel with capacity 0 to send STORAGE_SAVER_BATCH_SIZE blocks, -/// we guarantee that accounts and slots can either be found in the permanent -/// storage, or in the temporary one. -/// -/// In other words, at most (STORAGE_SAVER_BATCH_SIZE) * 2 executed blocks -/// won't be found in the permanent storage, but that's still within the -/// temporary storage capacity. -/// -/// We use half because we want parallelism in execution and persisting, both -/// places need to hold blocks that aren't in the permanent storage yet, it's -/// half for each. -const STORAGE_SAVER_BATCH_SIZE: usize = 64 / 2 - 1; +/// These constants are organized to guarantee that accounts and slots can +/// still be found in the storage cache. +const CACHE_SIZE: usize = 10_000; +const MAX_BLOCKS_NOT_SAVED: usize = CACHE_SIZE - 1; + +const BATCH_COUNT: usize = 10; +const SAVER_BATCH_SIZE: usize = MAX_BLOCKS_NOT_SAVED / BATCH_COUNT; +// The fetcher and saver tasks hold each, at most, SAVER_BATCH_SIZE blocks, +// so we need to subtract 2 from the buffer capacity to ensure we only have +// `CACHE_SIZE` executed blocks at a time. +const SAVER_CHANNEL_CAPACITY: usize = BATCH_COUNT - 2; type BlocksToExecute = Vec; type BlocksToSave = Vec; @@ -95,7 +94,7 @@ async fn run(config: ImporterOfflineConfig) -> anyhow::Result<()> { let (fetch_to_execute_tx, fetch_to_execute_rx) = async_mpsc::channel::(RPC_FETCHER_CHANNEL_CAPACITY); // send blocks from executor task to saver task - let (execute_to_save_tx, execute_to_save_rx) = mpsc::sync_channel::(0); + let (execute_to_save_tx, execute_to_save_rx) = mpsc::sync_channel::(SAVER_CHANNEL_CAPACITY); // load genesis accounts let initial_accounts = rpc_storage.read_initial_accounts().await?; @@ -232,8 +231,8 @@ fn run_external_block_executor( let instant_before_execution = Instant::now(); - for blocks in Itertools::chunks(blocks.into_iter(), STORAGE_SAVER_BATCH_SIZE).into_iter() { - let mut executed_batch = Vec::with_capacity(STORAGE_SAVER_BATCH_SIZE); + for blocks in Itertools::chunks(blocks.into_iter(), SAVER_BATCH_SIZE).into_iter() { + let mut executed_batch = Vec::with_capacity(SAVER_BATCH_SIZE); for (mut block, receipts) in blocks { if GlobalState::is_shutdown_warn(TASK_NAME) { diff --git a/src/eth/primitives/address.rs b/src/eth/primitives/address.rs index 8c89e07e8..d5f1409f7 100644 --- a/src/eth/primitives/address.rs +++ b/src/eth/primitives/address.rs @@ -21,8 +21,7 @@ use crate::eth::primitives::LogTopic; use crate::gen_newtype_from; /// Address of an Ethereum account (wallet or contract). -#[derive(DebugAsJson, Clone, Copy, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] -#[cfg_attr(test, derive(PartialOrd, Ord))] +#[derive(DebugAsJson, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub struct Address(pub H160); impl Address { diff --git a/src/eth/storage/cache.rs b/src/eth/storage/cache.rs new file mode 100644 index 000000000..78ad08d47 --- /dev/null +++ b/src/eth/storage/cache.rs @@ -0,0 +1,89 @@ +use std::num::NonZeroUsize; + +use lru::LruCache; +use parking_lot::Mutex; +use rustc_hash::FxBuildHasher; +use smallvec::SmallVec; + +use super::AccountWithSlots; +use crate::eth::primitives::Account; +use crate::eth::primitives::Address; +use crate::eth::primitives::ExecutionChanges; +use crate::eth::primitives::Slot; +use crate::eth::primitives::SlotIndex; +use crate::eth::primitives::SlotValue; + +pub struct StorageCache { + slot_cache: Mutex>, + account_cache: Mutex>, +} + +impl Default for StorageCache { + fn default() -> Self { + Self { + slot_cache: Mutex::new(LruCache::with_hasher(NonZeroUsize::new(100_000).unwrap(), FxBuildHasher)), + account_cache: Mutex::new(LruCache::with_hasher(NonZeroUsize::new(20_000).unwrap(), FxBuildHasher)), + } + } +} + +impl StorageCache { + pub fn clear(&self) { + self.slot_cache.lock().clear(); + self.account_cache.lock().clear(); + } + + pub fn cache_slot(&self, address: Address, slot: Slot) { + self.slot_cache.lock().put((address, slot.index), slot.value); + } + + pub fn cache_account(&self, account: Account) { + self.account_cache.lock().put(account.address, account); + } + + pub fn cache_account_and_slots_from_changes(&self, changes: ExecutionChanges) { + let mut slot_batch = SmallVec::<[_; 16]>::new(); + let mut account_batch = SmallVec::<[_; 8]>::new(); + + for change in changes.into_values() { + // cache slots + for slot in change.slots.into_values().flat_map(|slot| slot.take()) { + slot_batch.push(((change.address, slot.index), slot.value)); + } + + // cache account + let mut account = AccountWithSlots::new(change.address); + if let Some(nonce) = change.nonce.take_ref() { + account.info.nonce = *nonce; + } + if let Some(balance) = change.balance.take_ref() { + account.info.balance = *balance; + } + if let Some(Some(bytecode)) = change.bytecode.take_ref() { + account.info.bytecode = Some(bytecode.clone()); + } + account_batch.push((change.address, account.info)); + } + + { + let mut slot_lock = self.slot_cache.lock(); + for (key, value) in slot_batch { + slot_lock.push(key, value); + } + } + { + let mut account_lock = self.account_cache.lock(); + for (key, value) in account_batch { + account_lock.push(key, value); + } + } + } + + pub fn get_slot(&self, address: Address, index: SlotIndex) -> Option { + self.slot_cache.lock().get(&(address, index)).map(|&value| Slot { value, index }) + } + + pub fn get_account(&self, address: Address) -> Option { + self.account_cache.lock().get(&address).cloned() + } +} diff --git a/src/eth/storage/mod.rs b/src/eth/storage/mod.rs index 56125c681..f922fde17 100644 --- a/src/eth/storage/mod.rs +++ b/src/eth/storage/mod.rs @@ -1,5 +1,6 @@ //! Ethereum / EVM storage. +pub use cache::StorageCache; pub use permanent::InMemoryPermanentStorage; pub use permanent::PermanentStorage; pub use permanent::PermanentStorageConfig; @@ -11,6 +12,7 @@ pub use temporary::TemporaryStorage; pub use temporary::TemporaryStorageConfig; pub use temporary::TemporaryStorageKind; +mod cache; pub mod permanent; mod stratus_storage; mod temporary; diff --git a/src/eth/storage/stratus_storage.rs b/src/eth/storage/stratus_storage.rs index a9f6a86dd..2dc7793da 100644 --- a/src/eth/storage/stratus_storage.rs +++ b/src/eth/storage/stratus_storage.rs @@ -2,6 +2,7 @@ use anyhow::anyhow; use tracing::Span; use super::Storage; +use super::StorageCache; use crate::eth::primitives::Account; use crate::eth::primitives::Address; use crate::eth::primitives::Block; @@ -35,13 +36,18 @@ mod label { /// Additionaly it tracks metrics that are independent of the storage implementation. pub struct StratusStorage { temp: Box, + cache: StorageCache, perm: Box, } impl StratusStorage { /// Creates a new storage with the specified temporary and permanent implementations. pub fn new(temp: Box, perm: Box) -> Result { - let this = Self { temp, perm }; + let this = Self { + temp, + cache: StorageCache::default(), + perm, + }; // create genesis block and accounts if necessary #[cfg(feature = "dev")] @@ -143,78 +149,99 @@ impl Storage for StratusStorage { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("storage::read_account", %address, %point_in_time).entered(); - // read from temp only if requested - if point_in_time.is_pending() { - tracing::debug!(storage = %label::TEMP, %address, "reading account"); - let temp_account = timed(|| self.temp.read_account(address)).with(|m| { - metrics::inc_storage_read_account(m.elapsed, label::TEMP, point_in_time, m.result.is_ok()); + let account = 'query: { + if point_in_time.is_pending() { + if let Some(account) = self.cache.get_account(address) { + return Ok(account); + }; + + tracing::debug!(storage = %label::TEMP, %address, "reading account"); + let temp_account = timed(|| self.temp.read_account(address)).with(|m| { + metrics::inc_storage_read_account(m.elapsed, label::TEMP, point_in_time, m.result.is_ok()); + if let Err(ref e) = m.result { + tracing::error!(reason = ?e, "failed to read account from temporary storage"); + } + })?; + if let Some(account) = temp_account { + tracing::debug!(storage = %label::TEMP, %address, ?account, "account found in temporary storage"); + break 'query account; + } + } + + // always read from perm if necessary + tracing::debug!(storage = %label::PERM, %address, "reading account"); + let perm_account = timed(|| self.perm.read_account(address, point_in_time)).with(|m| { + metrics::inc_storage_read_account(m.elapsed, label::PERM, point_in_time, m.result.is_ok()); if let Err(ref e) = m.result { - tracing::error!(reason = ?e, "failed to read account from temporary storage"); + tracing::error!(reason = ?e, "failed to read account from permanent storage"); } })?; - if let Some(account) = temp_account { - tracing::debug!(storage = %label::TEMP, %address, ?account, "account found in temporary storage"); - return Ok(account); + match perm_account { + Some(account) => { + tracing::debug!(storage = %label::PERM, %address, ?account, "account found in permanent storage"); + account + } + None => { + tracing::debug!(storage = %label::PERM, %address, "account not found, assuming default value"); + Account::new_empty(address) + } } - } + }; - // always read from perm if necessary - tracing::debug!(storage = %label::PERM, %address, "reading account"); - let perm_account = timed(|| self.perm.read_account(address, point_in_time)).with(|m| { - metrics::inc_storage_read_account(m.elapsed, label::PERM, point_in_time, m.result.is_ok()); - if let Err(ref e) = m.result { - tracing::error!(reason = ?e, "failed to read account from permanent storage"); - } - })?; - match perm_account { - Some(account) => { - tracing::debug!(storage = %label::PERM, %address, ?account, "account found in permanent storage"); - Ok(account) - } - None => { - tracing::debug!(storage = %label::PERM, %address, "account not found, assuming default value"); - Ok(Account::new_empty(address)) - } + if point_in_time.is_pending() { + self.cache.cache_account(account.clone()); } + Ok(account) } fn read_slot(&self, address: Address, index: SlotIndex, point_in_time: PointInTime) -> Result { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("storage::read_slot", %address, %index, %point_in_time).entered(); - // read from temp only if requested - if point_in_time.is_pending() { - tracing::debug!(storage = %label::TEMP, %address, %index, "reading slot"); - let temp_slot = timed(|| self.temp.read_slot(address, index)).with(|m| { - metrics::inc_storage_read_slot(m.elapsed, label::TEMP, point_in_time, m.result.is_ok()); + let slot = 'query: { + if point_in_time.is_pending() { + if let Some(slot) = self.cache.get_slot(address, index) { + return Ok(slot); + }; + + tracing::debug!(storage = %label::TEMP, %address, %index, "reading slot"); + let temp_slot = timed(|| self.temp.read_slot(address, index)).with(|m| { + metrics::inc_storage_read_slot(m.elapsed, label::TEMP, point_in_time, m.result.is_ok()); + if let Err(ref e) = m.result { + tracing::error!(reason = ?e, "failed to read slot from temporary storage"); + } + })?; + if let Some(slot) = temp_slot { + tracing::debug!(storage = %label::TEMP, %address, %index, value = %slot.value, "slot found in temporary storage"); + break 'query slot; + } + } + + // always read from perm if necessary + tracing::debug!(storage = %label::PERM, %address, %index, %point_in_time, "reading slot"); + let perm_slot = timed(|| self.perm.read_slot(address, index, point_in_time)).with(|m| { + metrics::inc_storage_read_slot(m.elapsed, label::PERM, point_in_time, m.result.is_ok()); if let Err(ref e) = m.result { - tracing::error!(reason = ?e, "failed to read slot from temporary storage"); + tracing::error!(reason = ?e, "failed to read slot from permanent storage"); } })?; - if let Some(slot) = temp_slot { - tracing::debug!(storage = %label::TEMP, %address, %index, value = %slot.value, "slot found in temporary storage"); - return Ok(slot); - } - } - // always read from perm if necessary - tracing::debug!(storage = %label::PERM, %address, %index, %point_in_time, "reading slot"); - let perm_slot = timed(|| self.perm.read_slot(address, index, point_in_time)).with(|m| { - metrics::inc_storage_read_slot(m.elapsed, label::PERM, point_in_time, m.result.is_ok()); - if let Err(ref e) = m.result { - tracing::error!(reason = ?e, "failed to read slot from permanent storage"); - } - })?; - match perm_slot { - Some(slot) => { - tracing::debug!(storage = %label::PERM, %address, %index, value = %slot.value, "slot found in permanent storage"); - Ok(slot) - } - None => { - tracing::debug!(storage = %label::PERM, %address, %index, "slot not found, assuming default value"); - Ok(Slot::new_empty(index)) + match perm_slot { + Some(slot) => { + tracing::debug!(storage = %label::PERM, %address, %index, value = %slot.value, "slot found in permanent storage"); + slot + } + None => { + tracing::debug!(storage = %label::PERM, %address, %index, "slot not found, assuming default value"); + Slot::new_empty(index) + } } + }; + + if point_in_time.is_pending() { + self.cache.cache_slot(address, slot); } + Ok(slot) } // ------------------------------------------------------------------------- @@ -222,6 +249,8 @@ impl Storage for StratusStorage { // ------------------------------------------------------------------------- fn save_execution(&self, tx: TransactionExecution, check_conflicts: bool) -> Result<(), StratusError> { + let changes = tx.execution().changes.clone(); + #[cfg(feature = "tracing")] let _span = tracing::info_span!("storage::save_execution", tx_hash = %tx.hash()).entered(); tracing::debug!(storage = %label::TEMP, tx_hash = %tx.hash(), "saving execution"); @@ -238,6 +267,7 @@ impl Storage for StratusStorage { } }) .map_err(Into::into) + .inspect(|_| self.cache.cache_account_and_slots_from_changes(changes)) } /// Retrieves pending transactions being mined. @@ -430,6 +460,8 @@ impl Storage for StratusStorage { fn reset_to_genesis(&self) -> Result<(), StratusError> { use crate::eth::primitives::test_accounts; + self.cache.clear(); + tracing::info!("reseting storage to genesis state"); #[cfg(feature = "tracing")] diff --git a/src/eth/storage/temporary/inmemory.rs b/src/eth/storage/temporary/inmemory.rs index e1540aaa7..f8cb32a8c 100644 --- a/src/eth/storage/temporary/inmemory.rs +++ b/src/eth/storage/temporary/inmemory.rs @@ -29,7 +29,7 @@ use crate::eth::storage::AccountWithSlots; use crate::eth::storage::TemporaryStorage; /// Number of previous blocks to keep inmemory to detect conflicts between different blocks. -const MAX_BLOCKS: usize = 64; +const MAX_BLOCKS: usize = 1; #[derive(Debug)] pub struct InMemoryTemporaryStorage {