Skip to main content

miden_node_store/state/
tip.rs

1//! Live chain-tip queries and tip subscriptions, deliberately kept off
2//! [`StateView`](super::StateView).
3//!
4//! Both tips are published through watch channels by their single writers (the block writer for
5//! the committed tip, the proof scheduler or proof sync for the proven tip). Everything here
6//! reads or subscribes to those channels; none of it touches state snapshots — trees, forest, or
7//! DB — so these values advance independently of any `StateView`.
8//!
9//! This lives on [`State`] rather than `StateView` on purpose, not just because the values are
10//! live. A `StateView` pins a whole `StateSnapshot` (including the `RocksDB` snapshots backing
11//! the trees) and is meant to be released as soon as possible; a tip read never needs that
12//! snapshot, so routing it through a view would pin one for no reason. The `subscribe_*`
13//! receivers go further: they are meant to be held for the lifetime of a long-running task (a
14//! proof-sync loop, a subscription stream), which is the opposite of a `StateView`'s
15//! request-scoped, drop-it-immediately lifetime — so they could not live on `StateView` even if
16//! the values themselves needed one.
17
18use miden_protocol::block::BlockNumber;
19use tokio::sync::watch;
20
21use super::State;
22
23// TIP QUERIES & SUBSCRIPTIONS
24// ================================================================================================
25
26impl State {
27    /// Returns the latest committed (but not necessarily proven) block number.
28    ///
29    /// This is a live value: it advances independently of any [`StateView`](super::StateView).
30    /// Reads that must be consistent with data must use a view's tip instead.
31    ///
32    /// Ordering matters when combining this with a view: read the tip *before* creating the view.
33    /// Tips are monotonic and the committed tip is published after its snapshot, so a view
34    /// created afterwards can always serve a tip read earlier. Reading a live tip *after*
35    /// creating a view is racy — the tip may have advanced past the view's pinned snapshot.
36    pub fn committed_tip(&self) -> BlockNumber {
37        *self.committed_tip_tx.borrow()
38    }
39
40    /// Returns the latest block number proven in an unbroken sequence from genesis.
41    ///
42    /// This is a live value published by the proof scheduler (sequencer mode) or the proof sync
43    /// loop (full-node mode); it is always at or behind the committed tip.
44    ///
45    /// Ordering matters when combining this with a view: read the tip *before* creating the view.
46    /// Proofs are only applied to committed blocks and each committed tip is published after its
47    /// snapshot, so a view created afterwards can always serve a proven tip read earlier. Reading
48    /// it *after* creating a view is racy — a block may commit and prove concurrently, putting
49    /// the proven tip past the view's pinned snapshot.
50    pub fn proven_tip(&self) -> BlockNumber {
51        self.proven_tip.read()
52    }
53
54    /// Returns a watch receiver that wakes every time a new block is committed.
55    pub fn subscribe_committed_tip(&self) -> watch::Receiver<BlockNumber> {
56        self.committed_tip_tx.subscribe()
57    }
58
59    /// Returns a watch receiver that wakes every time the proven-in-sequence tip advances.
60    pub fn subscribe_proven_tip(&self) -> watch::Receiver<BlockNumber> {
61        self.proven_tip.subscribe()
62    }
63}