Skip to main content

confium_log_monitor/
main.rs

1//! `confium-log-monitor` — third-party monitor for Confium transparency logs.
2//!
3//! Watches a transparency log endpoint (e.g. `log.confium.org`),
4//! verifies internal consistency (every published tree head is a
5//! valid continuation of every prior tree head), and verifies
6//! external consistency (the log presents the same view to all
7//! monitors). Detects:
8//!
9//! - **Fork attempts**: the log presents different tree heads to
10//!   different monitors at the same tree size.
11//! - **Bad signatures**: the log's published signature doesn't
12//!   verify against the operational key.
13//! - **Bad inclusion proofs**: an inclusion proof doesn't
14//!   actually prove inclusion under the current root.
15//! - **Bad consistency proofs**: a consistency proof between
16//!   tree sizes M and N doesn't actually prove the trees are
17//!   related.
18//!
19//! ## Quickstart
20//!
21//! ```sh
22//! $ cargo run -p confium-log-monitor -- \
23//!     --log-url http://log.confium.org \
24//!     --state /var/lib/confium-monitor \
25//!     --poll-interval 30
26//! ```
27
28mod client;
29mod store;
30mod verify;
31
32use std::path::PathBuf;
33use std::time::Duration;
34
35use anyhow::Result;
36use clap::Parser;
37
38#[derive(Parser, Debug)]
39#[command(name = "confium-log-monitor", version)]
40pub struct Args {
41    /// Base URL of the transparency log to monitor.
42    #[arg(long, default_value = "http://127.0.0.1:8080")]
43    pub log_url: String,
44
45    /// Directory for persistent state (cached tree heads, witness sigs).
46    #[arg(long, default_value = "./confium-monitor-state")]
47    pub state: PathBuf,
48
49    /// Poll interval, in seconds.
50    #[arg(long, default_value_t = 30)]
51    pub poll_interval: u64,
52
53    /// Run once and exit (don't loop). Useful for cron-based monitoring.
54    #[arg(long)]
55    pub once: bool,
56}
57
58#[tokio::main]
59async fn main() -> Result<()> {
60    tracing_subscriber::fmt()
61        .with_env_filter(
62            tracing_subscriber::EnvFilter::try_from_default_env()
63                .unwrap_or_else(|_| "confium_log_monitor=info".into()),
64        )
65        .init();
66
67    let args = Args::parse();
68    let client = client::LogClient::new(args.log_url.clone());
69    let store = store::StateStore::open(&args.state)?;
70
71    loop {
72        if let Err(e) = run_cycle(&client, &store).await {
73            tracing::error!(?e, "monitor cycle failed");
74        }
75        if args.once {
76            return Ok(());
77        }
78        tokio::time::sleep(Duration::from_secs(args.poll_interval)).await;
79    }
80}
81
82async fn run_cycle(client: &client::LogClient, store: &store::StateStore) -> Result<()> {
83    let head = client.fetch_head().await?;
84    tracing::info!(tree_size = head.tree_size, root = %head.root, "fetched head");
85
86    let last_size = store.last_tree_size()?;
87    if head.tree_size > last_size {
88        // Verify consistency between last_size and head.tree_size.
89        if last_size > 0 {
90            let proof = client.fetch_consistency(last_size).await?;
91            let last_root = store.last_root()?;
92            verify::verify_consistency(&last_root, last_size, &head, &proof)?;
93            tracing::info!(
94                from = last_size,
95                to = head.tree_size,
96                "consistency verified"
97            );
98        }
99        // Cache the new head.
100        store.put_head(&head)?;
101    } else if head.tree_size < last_size {
102        // Tree size went backwards — this is a fork or a database reset.
103        tracing::error!(
104            observed = head.tree_size,
105            cached = last_size,
106            "TREE SIZE WENT BACKWARDS — possible fork"
107        );
108    }
109
110    Ok(())
111}