clementine_core/task/
entity_metric_publisher.rs

1use std::sync::LazyLock;
2use std::time::Duration;
3
4use tonic::async_trait;
5
6use crate::metrics::SyncStatusProvider;
7
8use crate::{
9    citrea::CitreaClientT,
10    database::Database,
11    extended_bitcoin_rpc::ExtendedBitcoinRpc,
12    metrics::ENTITY_SYNC_STATUS,
13    task::{Task, TaskVariant},
14    utils::NamedEntity,
15};
16use clementine_errors::BridgeError;
17
18/// The interval at which the entity metrics are polled and published
19/// (Not sent to Prometheus at this interval, since we use a pull-based http listener)
20///
21/// This doubles as the timeout for entity status retrieval.
22pub const ENTITY_METRIC_PUBLISHER_INTERVAL: Duration = Duration::from_secs(120);
23
24#[derive(Debug, Clone)]
25/// Publishes the metrics available for an entity (operator/verifier)
26pub struct EntityMetricPublisher<T: NamedEntity, C: CitreaClientT> {
27    db: Database,
28    rpc: ExtendedBitcoinRpc,
29    config: crate::config::BridgeConfig,
30    citrea_client: C,
31    _phantom: std::marker::PhantomData<T>,
32}
33
34impl<T: NamedEntity, C: CitreaClientT> EntityMetricPublisher<T, C> {
35    pub fn new(
36        db: Database,
37        rpc: ExtendedBitcoinRpc,
38        config: crate::config::BridgeConfig,
39        citrea_client: C,
40    ) -> Self {
41        Self {
42            db,
43            rpc,
44            config,
45            citrea_client,
46            _phantom: std::marker::PhantomData,
47        }
48    }
49}
50
51#[async_trait]
52impl<T: NamedEntity, C: CitreaClientT> Task for EntityMetricPublisher<T, C> {
53    const VARIANT: TaskVariant = TaskVariant::MetricPublisher;
54    type Output = bool;
55
56    async fn run_once(&mut self) -> Result<Self::Output, BridgeError> {
57        // Metrics are not published in tests
58        if cfg!(test) {
59            return Ok(false);
60        }
61
62        let sync_status = match T::get_sync_status(
63            &self.db,
64            &self.rpc,
65            &self.config,
66            &self.citrea_client,
67        )
68        .await
69        {
70            Ok(sync_status) => sync_status,
71            Err(e) => {
72                tracing::error!(
73                    "Failed to get status when publishing metrics for {}: {:?}",
74                    T::ENTITY_NAME,
75                    e
76                );
77
78                return Ok(false);
79            }
80        };
81
82        let metric = LazyLock::force(&ENTITY_SYNC_STATUS);
83
84        if let Some(balance) = sync_status.wallet_balance {
85            metric.wallet_balance_btc.set(balance.to_btc());
86        }
87        if let Some(height) = sync_status.rpc_tip_height {
88            metric.rpc_tip_height.set(height as f64);
89        }
90        if let Some(height) = sync_status.hcp_last_proven_height {
91            metric.hcp_last_proven_height.set(height as f64);
92        }
93        if let Some(height) = sync_status.btc_syncer_synced_height {
94            metric.btc_syncer_synced_height.set(height as f64);
95        }
96        if let Some(height) = sync_status.finalized_synced_height {
97            metric.finalized_synced_height.set(height as f64);
98        }
99        if let Some(height) = sync_status.tx_sender_synced_height {
100            metric.tx_sender_synced_height.set(height as f64);
101        }
102        if let Some(height) = sync_status.state_manager_next_height {
103            metric.state_manager_next_height.set(height as f64);
104        }
105        if let Some(fee_rate) = sync_status.bitcoin_fee_rate_sat_vb {
106            metric.bitcoin_fee_rate_sat_vb.set(fee_rate as f64);
107        }
108        if let Some(height) = sync_status.lcp_synced_height {
109            metric.lcp_synced_height.set(height as f64);
110        }
111        if let Some(height) = sync_status.citrea_l2_block_height {
112            metric.citrea_l2_block_height.set(height as f64);
113        }
114
115        Ok(false)
116    }
117}