quicknode-cascade 0.2.3

Stream blockchain data at scale. Plugin-based framework powered by QuickNode Cascade — start with Solana, more chains coming.
Documentation
//! File-based cursor for resume support.
//!
//! Tracks the last processed slot, total ingested/skipped counts.
//! Uses atomic write (tmp file + rename) to prevent corruption on crash.

use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;

/// Persisted cursor state for resume support.
///
/// Tracks the next slot to process and cumulative ingestion counts.
/// Written atomically (tmp + rename) to prevent corruption on crash.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CursorState {
    pub last_slot: u64,
    pub slots_ingested: u64,
    pub slots_skipped: u64,
}

impl Default for CursorState {
    fn default() -> Self {
        Self {
            last_slot: 0,
            slots_ingested: 0,
            slots_skipped: 0,
        }
    }
}

/// Load cursor state from a JSON file. Returns default (slot 0) if file doesn't exist or is corrupt.
pub fn load_cursor(path: &str) -> CursorState {
    let p = Path::new(path);
    if !p.exists() {
        return CursorState::default();
    }

    match std::fs::read_to_string(p) {
        Ok(data) => match serde_json::from_str::<CursorState>(&data) {
            Ok(cursor) => {
                tracing::info!(
                    "Loaded cursor: last_slot={}, ingested={}, skipped={}",
                    cursor.last_slot,
                    cursor.slots_ingested,
                    cursor.slots_skipped
                );
                cursor
            }
            Err(e) => {
                tracing::warn!("Cursor file corrupt ({}), starting fresh", e);
                CursorState::default()
            }
        },
        Err(e) => {
            tracing::warn!("Cannot read cursor file ({}), starting fresh", e);
            CursorState::default()
        }
    }
}

/// Save cursor state to a JSON file using atomic write (tmp + rename).
pub fn save_cursor(path: &str, state: &CursorState) -> Result<()> {
    let json = serde_json::to_string_pretty(state)?;
    let tmp = format!("{}.tmp", path);
    std::fs::write(&tmp, &json)?;
    std::fs::rename(&tmp, path)?;
    Ok(())
}