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}