From 3345a672b18aef20fa72c38689a172375784d273 Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 3 Jun 2026 09:18:26 +1200 Subject: [PATCH 1/3] Refactor fifo cache into block ordered cache --- crates/store/src/state/replica.rs | 16 +++- crates/store/src/state/subscription.rs | 9 +- crates/utils/src/block_cache.rs | 122 +++++++++++++++++++++++++ crates/utils/src/fifo_cache.rs | 109 ---------------------- crates/utils/src/lib.rs | 2 +- 5 files changed, 140 insertions(+), 118 deletions(-) create mode 100644 crates/utils/src/block_cache.rs delete mode 100644 crates/utils/src/fifo_cache.rs diff --git a/crates/store/src/state/replica.rs b/crates/store/src/state/replica.rs index 54d7b040ce..74a4681099 100644 --- a/crates/store/src/state/replica.rs +++ b/crates/store/src/state/replica.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use miden_node_utils::fifo_cache::FifoCache; +use miden_node_utils::block_cache::BlockOrderedCache; use miden_protocol::block::BlockNumber; // BLOCK NOTIFICATION @@ -12,7 +12,10 @@ pub struct BlockNotification(Arc); impl BlockNotification { pub fn new(block_num: BlockNumber, block_bytes: Vec) -> Self { - Self(Arc::new(Block { block_num, block_bytes })) + Self(Arc::new(Block { + block_num, + block_bytes, + })) } pub fn block_num(&self) -> BlockNumber { @@ -39,7 +42,10 @@ pub struct ProofNotification(Arc); impl ProofNotification { pub fn new(block_num: BlockNumber, proof_bytes: Vec) -> Self { - Self(Arc::new(Proof { block_num, proof_bytes })) + Self(Arc::new(Proof { + block_num, + proof_bytes, + })) } pub fn block_num(&self) -> BlockNumber { @@ -61,7 +67,7 @@ struct Proof { // ================================================================================================ /// FIFO cache of recent committed blocks for replica subscriptions. -pub type BlockCache = FifoCache; +pub type BlockCache = BlockOrderedCache; /// FIFO cache of recent block proofs for replica subscriptions. -pub type ProofCache = FifoCache; +pub type ProofCache = BlockOrderedCache; diff --git a/crates/store/src/state/subscription.rs b/crates/store/src/state/subscription.rs index 60ab2e9967..6537890e30 100644 --- a/crates/store/src/state/subscription.rs +++ b/crates/store/src/state/subscription.rs @@ -126,7 +126,10 @@ async fn run_block_stream( let block = fetch_block(next, &cache, &state).await?; tip = *tip_rx.borrow_and_update(); if tx - .send(Ok(BlockSubscriptionEvent { block, committed_chain_tip: tip })) + .send(Ok(BlockSubscriptionEvent { + block, + committed_chain_tip: tip, + })) .await .is_err() { @@ -177,7 +180,7 @@ async fn fetch_block( cache: &BlockCache, state: &State, ) -> Result, StateSubscriptionError> { - if let Some(entry) = cache.get(&block_num) { + if let Some(entry) = cache.get(block_num) { return Ok(entry.block_bytes().to_vec()); } state @@ -192,7 +195,7 @@ async fn fetch_proof( cache: &ProofCache, state: &State, ) -> Result, StateSubscriptionError> { - if let Some(entry) = cache.get(&block_num) { + if let Some(entry) = cache.get(block_num) { return Ok(entry.proof_bytes().to_vec()); } state diff --git a/crates/utils/src/block_cache.rs b/crates/utils/src/block_cache.rs new file mode 100644 index 0000000000..d0fe131598 --- /dev/null +++ b/crates/utils/src/block_cache.rs @@ -0,0 +1,122 @@ +use std::collections::VecDeque; +use std::num::NonZeroUsize; +use std::sync::{Arc, RwLock}; + +use miden_protocol::block::BlockNumber; + +type BlockOrderedQueue = VecDeque<(BlockNumber, T)>; + +/// A cheaply cloneable block-ordered cache. +#[derive(Clone)] +pub struct BlockOrderedCache { + inner: Arc>>, + capacity: usize, +} + +impl BlockOrderedCache { + /// Creates a new cache with the given capacity. + pub fn new(capacity: NonZeroUsize) -> Self { + Self { + inner: Arc::new(RwLock::new(VecDeque::new())), + capacity: capacity.get(), + } + } + + /// Pushes a new value into the cache and evicts the oldest value if the cache is full. + /// + /// # Panics + /// + /// Panics if the provided block number is not a child of the youngest block in the cache. + pub fn push(&self, number: BlockNumber, value: T) { + let mut fifo = self.inner.write().expect("fifo cache lock poisoned"); + + if let Some((youngest, _)) = fifo.back() { + assert_eq!(youngest.child(), number); + } + + if fifo.len() == self.capacity { + fifo.pop_front(); + } + + fifo.push_back((number, value)); + } +} + +impl BlockOrderedCache { + /// Retrieves the value associated with the given block number from the cache. + pub fn get(&self, number: BlockNumber) -> Option { + let fifo = self.inner.read().expect("fifo cache lock poisoned"); + let (oldest, _) = fifo.front()?; + + let offset = number.checked_sub(oldest.as_u32())?; + let (_, value) = fifo.get(offset.as_usize())?; + Some(value.clone()) + } +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroUsize; + + use super::BlockOrderedCache; + + fn cache(cap: usize) -> BlockOrderedCache<&'static str> { + BlockOrderedCache::new(NonZeroUsize::new(cap).unwrap()) + } + + #[test] + fn get_returns_none_on_empty_cache() { + let c = cache(4); + assert_eq!(c.get(1.into()), None); + } + + #[test] + fn get_returns_inserted_value() { + let c = cache(4); + c.push(1.into(), "a"); + assert_eq!(c.get(1.into()), Some("a")); + } + + #[test] + fn evicts_oldest_entry_when_full() { + let c = cache(2); + c.push(5.into(), "a"); + c.push(6.into(), "b"); + c.push(7.into(), "c"); // evicts 1 + assert_eq!(c.get(5.into()), None); + assert_eq!(c.get(6.into()), Some("b")); + assert_eq!(c.get(7.into()), Some("c")); + } + + #[test] + #[should_panic] + fn overwrite_key_panics() { + let c = cache(2); + c.push(1.into(), "a"); + c.push(1.into(), "b"); + } + + #[test] + #[should_panic] + fn parent_panics() { + let c = cache(2); + c.push(3.into(), "a"); + c.push(2.into(), "b"); + } + + #[test] + #[should_panic] + fn wrong_child_panics() { + let c = cache(2); + c.push(1.into(), "a"); + c.push(3.into(), "b"); + } + + #[test] + fn clone_shares_state() { + let c1 = cache(4); + let c2 = c1.clone(); + c1.push(1.into(), "a"); + assert_eq!(c2.get(1.into()), Some("a")); + } +} diff --git a/crates/utils/src/fifo_cache.rs b/crates/utils/src/fifo_cache.rs deleted file mode 100644 index df10793d11..0000000000 --- a/crates/utils/src/fifo_cache.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::hash::Hash; -use std::num::NonZeroUsize; -use std::sync::{Arc, Mutex}; - -/// A fixed-capacity FIFO cache keyed by `K`. -/// -/// When full, the oldest entry (by insertion order) is evicted to make room. Uses a [`HashMap`] -/// for O(1) lookup and a [`VecDeque`] to track insertion order for eviction. -/// -/// Wraps the inner state in `Arc` so it can be cheaply cloned and shared across threads -/// without holding the lock across await points. -#[derive(Clone)] -pub struct FifoCache(Arc>>); - -struct Inner { - map: HashMap, - eviction: VecDeque, - capacity: NonZeroUsize, -} - -impl FifoCache -where - K: Hash + Eq + Clone, - V: Clone, -{ - /// Creates a new cache with the given capacity. - pub fn new(capacity: NonZeroUsize) -> Self { - Self(Arc::new(Mutex::new(Inner { - map: HashMap::new(), - eviction: VecDeque::new(), - capacity, - }))) - } - - /// Returns a clone of the value associated with `key`, or `None` if not present. - pub fn get(&self, key: &K) -> Option { - self.0.lock().expect("fifo cache lock poisoned").map.get(key).cloned() - } - - /// Inserts a key-value pair, evicting the oldest entry if the cache is at capacity. - pub fn push(&self, key: K, value: V) { - let mut inner = self.0.lock().expect("fifo cache lock poisoned"); - if inner.eviction.len() >= inner.capacity.get() { - if let Some(oldest) = inner.eviction.pop_front() { - inner.map.remove(&oldest); - } - } - inner.eviction.push_back(key.clone()); - inner.map.insert(key, value); - } -} - -#[cfg(test)] -mod tests { - use std::num::NonZeroUsize; - - use super::FifoCache; - - fn cache(cap: usize) -> FifoCache { - FifoCache::new(NonZeroUsize::new(cap).unwrap()) - } - - #[test] - fn get_returns_none_on_empty_cache() { - let c = cache(4); - assert_eq!(c.get(&1), None); - } - - #[test] - fn get_returns_inserted_value() { - let c = cache(4); - c.push(1, "a"); - assert_eq!(c.get(&1), Some("a")); - } - - #[test] - fn evicts_oldest_entry_when_full() { - let c = cache(2); - c.push(1, "a"); - c.push(2, "b"); - c.push(3, "c"); // evicts 1 - assert_eq!(c.get(&1), None); - assert_eq!(c.get(&2), Some("b")); - assert_eq!(c.get(&3), Some("c")); - } - - #[test] - fn overwrite_key_evicts_on_next_push() { - // Pushing the same key twice leaves a ghost entry in the eviction queue. The ghost is a - // no-op when it surfaces as the oldest entry: the key is already absent from the map so - // map.remove() does nothing. The important invariant is that no *other* key is spuriously - // evicted. - let c = cache(2); - c.push(1, "a"); - c.push(1, "b"); // eviction queue: [1, 1], map: {1: "b"} - c.push(2, "c"); // evicts ghost front (key 1), map: {2: "c"} - assert_eq!(c.get(&1), None); // 1 was evicted - assert_eq!(c.get(&2), Some("c")); // 2 survived - } - - #[test] - fn clone_shares_state() { - let c1 = cache(4); - let c2 = c1.clone(); - c1.push(1, "a"); - assert_eq!(c2.get(&1), Some("a")); - } -} diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index bc29ffc15a..96e81b49a4 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -1,9 +1,9 @@ +pub mod block_cache; pub mod clap; pub mod cors; pub mod crypto; #[cfg(feature = "testing")] pub mod fee; -pub mod fifo_cache; pub mod formatting; pub mod fs; pub mod genesis; From 94a99bf94b43bccc21c3ef142bae818cc008f87d Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 3 Jun 2026 10:37:16 +1200 Subject: [PATCH 2/3] Lint --- .../samples/02-with-account-files/bridge.mac | Bin 37762 -> 37762 bytes crates/store/src/state/replica.rs | 10 ++-------- crates/store/src/state/subscription.rs | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac b/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac index 6437e37b2a98f85a7cf5d6faa21d534ec0f49f01..8412cfe5cb431e2e9b554f126ab3e959d5d57864 100644 GIT binary patch delta 3925 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 delta 3925 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 impl BlockNotification { pub fn new(block_num: BlockNumber, block_bytes: Vec) -> Self { - Self(Arc::new(Block { - block_num, - block_bytes, - })) + Self(Arc::new(Block { block_num, block_bytes })) } pub fn block_num(&self) -> BlockNumber { @@ -42,10 +39,7 @@ pub struct ProofNotification(Arc); impl ProofNotification { pub fn new(block_num: BlockNumber, proof_bytes: Vec) -> Self { - Self(Arc::new(Proof { - block_num, - proof_bytes, - })) + Self(Arc::new(Proof { block_num, proof_bytes })) } pub fn block_num(&self) -> BlockNumber { diff --git a/crates/store/src/state/subscription.rs b/crates/store/src/state/subscription.rs index 6537890e30..9931487c14 100644 --- a/crates/store/src/state/subscription.rs +++ b/crates/store/src/state/subscription.rs @@ -126,10 +126,7 @@ async fn run_block_stream( let block = fetch_block(next, &cache, &state).await?; tip = *tip_rx.borrow_and_update(); if tx - .send(Ok(BlockSubscriptionEvent { - block, - committed_chain_tip: tip, - })) + .send(Ok(BlockSubscriptionEvent { block, committed_chain_tip: tip })) .await .is_err() { From a278cd23ba01ce587fdc7e1cf6c927947207f71a Mon Sep 17 00:00:00 2001 From: Mirko von Leipzig <48352201+Mirko-von-Leipzig@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:52:05 +0200 Subject: [PATCH 3/3] Review suggestions --- .../samples/02-with-account-files/bridge.mac | Bin 37762 -> 37762 bytes crates/store/src/state/apply_block.rs | 4 +- crates/store/src/state/apply_proof.rs | 4 +- crates/utils/src/block_cache.rs | 84 ++++++++++-------- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac b/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac index 8412cfe5cb431e2e9b554f126ab3e959d5d57864..6437e37b2a98f85a7cf5d6faa21d534ec0f49f01 100644 GIT binary patch delta 3925 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 diff --git a/crates/store/src/state/apply_block.rs b/crates/store/src/state/apply_block.rs index 8eea44b621..2992573bce 100644 --- a/crates/store/src/state/apply_block.rs +++ b/crates/store/src/state/apply_block.rs @@ -181,7 +181,9 @@ impl State { })?; // Push to cache and notify replica subscribers. - self.block_cache.push(block_num, BlockNotification::new(block_num, cache_bytes)); + self.block_cache + .push(block_num, BlockNotification::new(block_num, cache_bytes)) + .expect("block cache receives sequential block numbers"); let _ = self.committed_tip_tx.send(block_num); info!(%block_commitment, block_num = block_num.as_u32(), COMPONENT, "apply_block successful"); diff --git a/crates/store/src/state/apply_proof.rs b/crates/store/src/state/apply_proof.rs index 018076ee28..4fdcb24f54 100644 --- a/crates/store/src/state/apply_proof.rs +++ b/crates/store/src/state/apply_proof.rs @@ -13,7 +13,9 @@ impl State { proof_bytes: Vec, ) -> anyhow::Result<()> { self.block_store.commit_proof(block_num, &proof_bytes).await?; - self.proof_cache.push(block_num, ProofNotification::new(block_num, proof_bytes)); + self.proof_cache + .push(block_num, ProofNotification::new(block_num, proof_bytes)) + .expect("proof cache receives sequential block numbers"); self.proven_tip.advance(block_num); Ok(()) } diff --git a/crates/utils/src/block_cache.rs b/crates/utils/src/block_cache.rs index d0fe131598..1e6b07e3aa 100644 --- a/crates/utils/src/block_cache.rs +++ b/crates/utils/src/block_cache.rs @@ -4,12 +4,15 @@ use std::sync::{Arc, RwLock}; use miden_protocol::block::BlockNumber; -type BlockOrderedQueue = VecDeque<(BlockNumber, T)>; - /// A cheaply cloneable block-ordered cache. #[derive(Clone)] pub struct BlockOrderedCache { - inner: Arc>>, + inner: Arc>>, +} + +struct Inner { + fifo: VecDeque, + youngest: Option, capacity: usize, } @@ -17,40 +20,49 @@ impl BlockOrderedCache { /// Creates a new cache with the given capacity. pub fn new(capacity: NonZeroUsize) -> Self { Self { - inner: Arc::new(RwLock::new(VecDeque::new())), - capacity: capacity.get(), + inner: Arc::new(RwLock::new(Inner { + fifo: VecDeque::new(), + youngest: None, + capacity: capacity.get(), + })), } } /// Pushes a new value into the cache and evicts the oldest value if the cache is full. /// - /// # Panics + /// # Error /// - /// Panics if the provided block number is not a child of the youngest block in the cache. - pub fn push(&self, number: BlockNumber, value: T) { - let mut fifo = self.inner.write().expect("fifo cache lock poisoned"); - - if let Some((youngest, _)) = fifo.back() { - assert_eq!(youngest.child(), number); + /// Returns the value if the provided block number is not the next in sequence. + pub fn push(&self, number: BlockNumber, value: T) -> Result<(), T> { + let mut inner = self.inner.write().expect("block cache lock poisoned"); + + if let Some(youngest) = inner.youngest { + if youngest.child() != number { + return Err(value); + } } - if fifo.len() == self.capacity { - fifo.pop_front(); + if inner.fifo.len() >= inner.capacity { + inner.fifo.pop_front(); } - fifo.push_back((number, value)); + inner.fifo.push_back(value); + inner.youngest = Some(number); + + Ok(()) } } impl BlockOrderedCache { /// Retrieves the value associated with the given block number from the cache. pub fn get(&self, number: BlockNumber) -> Option { - let fifo = self.inner.read().expect("fifo cache lock poisoned"); - let (oldest, _) = fifo.front()?; + let inner = self.inner.read().expect("block cache lock poisoned"); + let youngest = inner.youngest?; + let distance_to_oldest = u32::try_from(inner.fifo.len().checked_sub(1)?).ok()?; + let oldest = youngest.checked_sub(distance_to_oldest)?; let offset = number.checked_sub(oldest.as_u32())?; - let (_, value) = fifo.get(offset.as_usize())?; - Some(value.clone()) + inner.fifo.get(offset.as_usize()).cloned() } } @@ -73,50 +85,50 @@ mod tests { #[test] fn get_returns_inserted_value() { let c = cache(4); - c.push(1.into(), "a"); + assert_eq!(c.push(1.into(), "a"), Ok(())); assert_eq!(c.get(1.into()), Some("a")); } #[test] fn evicts_oldest_entry_when_full() { let c = cache(2); - c.push(5.into(), "a"); - c.push(6.into(), "b"); - c.push(7.into(), "c"); // evicts 1 + assert_eq!(c.push(5.into(), "a"), Ok(())); + assert_eq!(c.push(6.into(), "b"), Ok(())); + assert_eq!(c.push(7.into(), "c"), Ok(())); // evicts 5 assert_eq!(c.get(5.into()), None); assert_eq!(c.get(6.into()), Some("b")); assert_eq!(c.get(7.into()), Some("c")); } #[test] - #[should_panic] - fn overwrite_key_panics() { + fn overwrite_key_returns_value() { let c = cache(2); - c.push(1.into(), "a"); - c.push(1.into(), "b"); + assert_eq!(c.push(1.into(), "a"), Ok(())); + assert_eq!(c.push(1.into(), "b"), Err("b")); + assert_eq!(c.get(1.into()), Some("a")); } #[test] - #[should_panic] - fn parent_panics() { + fn parent_returns_value() { let c = cache(2); - c.push(3.into(), "a"); - c.push(2.into(), "b"); + assert_eq!(c.push(3.into(), "a"), Ok(())); + assert_eq!(c.push(2.into(), "b"), Err("b")); + assert_eq!(c.get(3.into()), Some("a")); } #[test] - #[should_panic] - fn wrong_child_panics() { + fn wrong_child_returns_value() { let c = cache(2); - c.push(1.into(), "a"); - c.push(3.into(), "b"); + assert_eq!(c.push(1.into(), "a"), Ok(())); + assert_eq!(c.push(3.into(), "b"), Err("b")); + assert_eq!(c.get(1.into()), Some("a")); } #[test] fn clone_shares_state() { let c1 = cache(4); let c2 = c1.clone(); - c1.push(1.into(), "a"); + assert_eq!(c1.push(1.into(), "a"), Ok(())); assert_eq!(c2.get(1.into()), Some("a")); } }