Skip to main content

brk_query/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(clippy::module_inception)]
3
4use std::{
5    path::Path,
6    sync::{Arc, RwLock},
7};
8
9use brk_computer::Computer;
10use brk_error::{OptionData, Result};
11use brk_indexer::{Indexer, Lengths};
12use brk_mempool::Mempool;
13use brk_oracle::Oracle;
14use brk_reader::Reader;
15use brk_rpc::Client;
16use brk_types::{BlockHash, BlockHashPrefix, Height, SyncStatus};
17use vecdb::{ReadOnlyClone, ReadableVec, Ro};
18
19#[cfg(feature = "tokio")]
20mod r#async;
21mod vecs;
22
23mod r#impl;
24
25#[cfg(feature = "tokio")]
26pub use r#async::*;
27pub use r#impl::ResolvedQuery;
28pub use vecs::Vecs;
29
30#[derive(Clone)]
31pub struct Query(Arc<QueryInner<'static>>);
32struct QueryInner<'a> {
33    vecs: &'a Vecs<'a>,
34    client: Client,
35    reader: Reader,
36    indexer: &'a Indexer<Ro>,
37    computer: &'a Computer<Ro>,
38    mempool: Option<Mempool>,
39    live_oracle: RwLock<Option<(Height, Arc<Oracle>)>>,
40}
41
42impl Query {
43    pub fn build(
44        reader: &Reader,
45        indexer: &Indexer,
46        computer: &Computer,
47        mempool: Option<Mempool>,
48    ) -> Self {
49        let client = reader.client().clone();
50        let reader = reader.clone();
51        let indexer = Box::leak(Box::new(indexer.read_only_clone()));
52        let computer = Box::leak(Box::new(computer.read_only_clone()));
53        let vecs = Box::leak(Box::new(Vecs::build(indexer, computer)));
54
55        Self(Arc::new(QueryInner {
56            vecs,
57            client,
58            reader,
59            indexer,
60            computer,
61            mempool,
62            live_oracle: RwLock::new(None),
63        }))
64    }
65
66    /// Pipeline-safe ceiling: the highest height for which both the
67    /// indexer and computer have committed durable data. Backed by
68    /// `Indexer::safe_lengths()`, advanced by `main.rs` after each
69    /// compute pass and lowered before any rollback.
70    ///
71    /// Returns a height (the last fully-written block), not a length.
72    /// `safe_lengths().height` is a count: `N` means heights `0..N` are
73    /// committed, so the highest is `N-1`. Pre-genesis (`N == 0`) falls
74    /// back to `Height::default()` and clients treat it as "nothing
75    /// indexed yet".
76    pub fn height(&self) -> Height {
77        self.safe_lengths().height.decremented().unwrap_or_default()
78    }
79
80    /// Snapshot of the pipeline-safe `Lengths`. Hot paths that need
81    /// multiple bound fields should call this once at entry and reuse.
82    pub(crate) fn safe_lengths(&self) -> Lengths {
83        self.indexer().safe_lengths()
84    }
85
86    /// Tip block hash at the pipeline-safe ceiling.
87    #[inline]
88    pub fn tip_blockhash(&self) -> BlockHash {
89        self.indexer().tip_blockhash()
90    }
91
92    /// Tip block hash prefix for cache etags.
93    #[inline]
94    pub fn tip_hash_prefix(&self) -> BlockHashPrefix {
95        BlockHashPrefix::from(&self.tip_blockhash())
96    }
97
98    /// Build sync status with the given tip height. `indexed_height` and
99    /// `computed_height` reflect live per-vec stamps (diagnostic) and may be
100    /// briefly ahead of fully-flushed data; the timestamp data read uses the
101    /// safe-lengths-derived height so it never outruns committed bytes.
102    pub fn sync_status(&self, tip_height: Height) -> Result<SyncStatus> {
103        let indexed_height = self.indexer().indexed_height();
104        let computed_height = self.computer().computed_height();
105        let blocks_behind = Height::from(tip_height.saturating_sub(*indexed_height));
106        let last_indexed_at_unix = self
107            .indexer()
108            .vecs
109            .blocks
110            .timestamp
111            .collect_one(self.height())
112            .data()?;
113
114        Ok(SyncStatus {
115            indexed_height,
116            computed_height,
117            tip_height,
118            blocks_behind,
119            last_indexed_at: last_indexed_at_unix.to_iso8601(),
120            last_indexed_at_unix,
121        })
122    }
123
124    #[inline]
125    pub fn reader(&self) -> &Reader {
126        &self.0.reader
127    }
128
129    #[inline]
130    pub fn client(&self) -> &Client {
131        &self.0.client
132    }
133
134    #[inline]
135    pub fn blocks_dir(&self) -> &Path {
136        self.0.reader.blocks_dir()
137    }
138
139    #[inline]
140    pub fn indexer(&self) -> &Indexer<Ro> {
141        self.0.indexer
142    }
143
144    #[inline]
145    pub fn computer(&self) -> &Computer<Ro> {
146        self.0.computer
147    }
148
149    #[inline]
150    pub fn mempool(&self) -> Option<&Mempool> {
151        self.0.mempool.as_ref()
152    }
153
154    #[inline]
155    pub fn vecs(&self) -> &'static Vecs<'static> {
156        self.0.vecs
157    }
158}