Skip to main content

jetstreamer_firehose/
lib.rs

1#![deny(missing_docs)]
2#![recursion_limit = "512"]
3//! Core data structures and streaming utilities for Jetstreamer firehose processing.
4//!
5//! # Overview
6//! The firehose crate streams data live over the network directly from Project Yellowstone's
7//! [Old Faithful](https://old-faithful.net/) archive of CAR files, which hosts the complete
8//! history of every Solana transaction. Data only flows outward from Old Faithful to your
9//! local consumer; nothing is ever uploaded back to the archive. With sufficient CPU and
10//! network headroom the pipeline can exceed 2.7 million transactions per second while decoding
11//! the stream for analysis and backfilling workloads.
12//!
13//! Firehose is the foundation that powers
14//! [`jetstreamer`](https://crates.io/crates/jetstreamer) and
15//! [`jetstreamer-plugin`](https://crates.io/crates/jetstreamer-plugin), but it can also be
16//! consumed directly to build bespoke replay pipelines. The crate exposes:
17//! - Async readers for Old Faithful CAR archives via [`firehose`].
18//! - Rich data models for blocks, entries, rewards, and transactions.
19//! - Epoch helpers for reasoning about slot ranges and availability windows.
20//!
21//! # Configuration
22//! Several environment variables influence how the firehose locates and caches data:
23//! - `JETSTREAMER_HTTP_BASE_URL` (default `https://files.old-faithful.net`): base URL or
24//!   `s3://bucket/prefix` for CAR snapshots. Change this to point at a private mirror.
25//! - `JETSTREAMER_COMPACT_INDEX_BASE_URL` (default `https://files.old-faithful.net`): base URL
26//!   for slot index artifacts (the per-epoch `slot-ranges.raw` file, plus the deprecated
27//!   legacy compactindexes used as a fallback). Point this at your own mirror to reduce load
28//!   on the public Old Faithful deployment. Also supports `s3://` URIs.
29//! - `JETSTREAMER_FORCE_LEGACY_INDEX`: set to `1` to skip the slot-ranges index and resolve
30//!   slot offsets using the deprecated legacy compactindex pair.
31//! - `JETSTREAMER_ARCHIVE_BASE`: fallback URL/URI that applies to both CARs and compact
32//!   indexes when the more specific knobs are unset.
33//! - `JETSTREAMER_ARCHIVE_BACKEND` (default `http`): set to `s3` to force the S3 transport even
34//!   when the resolved URL still points at `https://`.
35//! - `JETSTREAMER_S3_BUCKET`, `JETSTREAMER_S3_PREFIX`, `JETSTREAMER_S3_INDEX_PREFIX`,
36//!   `JETSTREAMER_S3_REGION`, `JETSTREAMER_S3_ENDPOINT`, `JETSTREAMER_S3_ACCESS_KEY`,
37//!   `JETSTREAMER_S3_SECRET_KEY`, `JETSTREAMER_S3_SESSION_TOKEN`: credentials and overrides
38//!   applied when the S3 backend is active.
39//! - `JETSTREAMER_NETWORK` (default `mainnet`): suffix appended to cache namespaces and index
40//!   filenames so you can swap between clusters without purging local state.
41//! - `JETSTREAMER_SPAWN_PENDING` (default `24`): maximum not-yet-green threads in flight
42//!   during the health-gated thread ramp; `1` reproduces a strict one-at-a-time ramp.
43//! - `JETSTREAMER_SPAWN_GRACE_SECS` (default `30`): how long the launch gate waits for
44//!   sluggish (never stalled) threads before spawning anyway; `0` disables launch gating.
45//! - `JETSTREAMER_RECYCLE_PCT` (default `50`): connection-recycle threshold as a percent of
46//!   the best observed p90 per-thread rate; `0` disables recycling.
47//! - `JETSTREAMER_NETWORK_CAPACITY_MB` (default `1000`): assumed network throughput in megabytes
48//!   per second used when sizing the firehose thread pool. Increase or decrease to match your
49//!   host's effective bandwidth.
50//!
51//! S3 transports are compiled behind the `s3-backend` Cargo feature. Enable that feature when
52//! building if you plan to stream from `s3://` URIs instead of HTTP mirrors.
53//!
54//! Sequential-mode ripget buffering is configured on the [`firehose::firehose`] call via
55//! `buffer_window_bytes`. Pass `None` to use the built-in default (`min(4 GiB, 15% of available
56//! RAM)`). If you're using [`jetstreamer`](https://crates.io/crates/jetstreamer)'s binary runner,
57//! that layer exposes `JETSTREAMER_BUFFER_WINDOW` and forwards the parsed value here.
58//!
59//! # Limitations
60//! Old Faithful currently publishes blocks, transactions, epochs, and reward metadata but does
61//! not ship account updates. The firehose mirrors that limitation; plan on a separate data
62//! source if you require account updates.
63//!
64//! # Epoch Feature Availability
65//! Old Faithful snapshots expose different metadata as the Solana protocol evolved. Use the
66//! table below to decide which replay windows fit your requirements:
67//!
68//! | Epoch range | Slot range    | Comment |
69//! |-------------|---------------|--------------------------------------------------|
70//! | 0–156       | 0–?           | Incompatible with modern Geyser plugins          |
71//! | 157+        | ?             | Compatible with modern Geyser plugins            |
72//! | 0–449       | 0–194184610   | CU tracking not available (reported as `0`)      |
73//! | 450+        | 194184611+    | CU tracking fully available                      |
74//!
75//! Detailed helpers for translating between epochs and slots live in the [`epochs`] module.
76//!
77//! # Ordering Guarantees
78//! Because [`firehose`] spawns parallel threads that process different subranges of the
79//! overall slot range at the same time, while each thread sees a purely sequential view of
80//! transactions, downstream services such as databases that consume this data will see writes
81//! in a fairly arbitrary order, so you should design your database tables and shared data
82//! structures accordingly. The best pattern is to aggregate data on some interval on a
83//! thread-local, per-thread basis and periodically flush the aggregated data to the shared
84//! downstream service or data structure.
85//!
86//! # Examples
87//! Run the firehose with handlers for every data type:
88//! ```no_run
89//! use futures_util::FutureExt;
90//! use jetstreamer_firehose::{
91//!     epochs,
92//!     firehose::{self, BlockData, EntryData, RewardsData, Stats, StatsTracking, TransactionData},
93//! };
94//!
95//! fn block_handler() -> impl firehose::Handler<BlockData> {
96//!     move |_thread_id, block| async move {
97//!         println!("block slot {}", block.slot());
98//!         Ok(())
99//!     }
100//!     .boxed()
101//! }
102//!
103//! fn tx_handler() -> impl firehose::Handler<TransactionData> {
104//!     move |_thread_id, tx| async move {
105//!         println!("tx {} in slot {}", tx.signature, tx.slot);
106//!         Ok(())
107//!     }
108//!     .boxed()
109//! }
110//!
111//! fn entry_handler() -> impl firehose::Handler<EntryData> {
112//!     move |_thread_id, entry| async move {
113//!         println!("entry {} covering transactions {:?}", entry.entry_index, entry.transaction_indexes);
114//!         Ok(())
115//!     }
116//!     .boxed()
117//! }
118//!
119//! fn reward_handler() -> impl firehose::Handler<RewardsData> {
120//!     move |_thread_id, rewards| async move {
121//!         println!("rewards in slot {} -> {} accounts", rewards.slot, rewards.rewards.len());
122//!         Ok(())
123//!     }
124//!     .boxed()
125//! }
126//!
127//! fn stats_handler() -> impl firehose::Handler<Stats> {
128//!     move |_thread_id, stats| async move {
129//!         println!("processed {} slots so far", stats.slots_processed);
130//!         Ok(())
131//!     }
132//!     .boxed()
133//! }
134//!
135//! #[tokio::main]
136//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
137//!     let stats = StatsTracking {
138//!         on_stats: stats_handler(),
139//!         tracking_interval_slots: 100,
140//!     };
141//!
142//!     let (start, _) = epochs::epoch_to_slot_range(800);
143//!     let (_, end_inclusive) = epochs::epoch_to_slot_range(805);
144//!     let slot_range = start..(end_inclusive + 1);
145//!
146//!     firehose::firehose(
147//!         4,
148//!         false,
149//!         false,
150//!         None,
151//!         slot_range,
152//!         Some(block_handler()),
153//!         Some(tx_handler()),
154//!         Some(entry_handler()),
155//!         Some(reward_handler()),
156//!         None::<firehose::OnErrorFn>,
157//!         Some(stats),
158//!         None,
159//!     )
160//!     .await
161//!     .map_err(|(err, slot)| -> Box<dyn std::error::Error> {
162//!         format!("firehose failed at slot {slot}: {err}").into()
163//!     })?;
164//!     Ok(())
165//! }
166//! ```
167
168/// Shared boxed error type propagated across firehose modules.
169pub type SharedError = Box<dyn std::error::Error + Send + Sync + 'static>;
170
171/// Backend configuration for archive mirrors (HTTP and S3).
172pub mod archive;
173/// Types for decoding block-level records emitted by the firehose.
174pub mod block;
175/// Encodes and decodes arbitrary binary [`DataFrame`](dataframe::DataFrame) nodes.
176pub mod dataframe;
177/// Parsing and serialization helpers for [`Entry`](entry::Entry) nodes.
178pub mod entry;
179/// Structures for the top-level [`Epoch`](epoch::Epoch) node type.
180pub mod epoch;
181/// Epoch utilities such as [`epoch_to_slot_range`](epochs::epoch_to_slot_range).
182pub mod epochs;
183/// Streaming interface for fetching and parsing firehose blocks.
184pub mod firehose;
185/// Slot offset index client for locating blocks in Old Faithful CAR archives.
186pub mod index;
187/// Helpers for working with network metadata and endpoints.
188pub mod network;
189/// Core node tree definitions shared across firehose types.
190pub mod node;
191/// Reader utilities for decoding Old Faithful CAR node streams.
192pub mod node_reader;
193/// Reward decoding primitives and helpers.
194pub mod rewards;
195/// Utilities for working with subset nodes.
196pub mod subset;
197/// System heuristics for sizing the firehose runtime.
198pub mod system;
199/// Transaction decoding and helpers.
200pub mod transaction;
201/// Shared helpers used throughout the firehose crate.
202pub mod utils;
203
204pub use firehose::{BlockData, EntryData, RewardsData, TransactionData};
205
206/// Log target prefix used across the firehose crate.
207pub const LOG_MODULE: &str = "jetstreamer::firehose";