Skip to main content

fedimint_server_bitcoin_rpc/
tracked.rs

1use anyhow::Result;
2use bitcoin::{Block, BlockHash, Transaction};
3use fedimint_core::envs::BitcoinRpcConfig;
4use fedimint_core::time::now;
5use fedimint_core::util::{FmtCompactResult as _, SafeUrl};
6use fedimint_core::{ChainId, Feerate};
7use fedimint_logging::LOG_BITCOIND;
8use fedimint_metrics::HistogramExt as _;
9use fedimint_server_core::bitcoin_rpc::{DynServerBitcoinRpc, IServerBitcoinRpc};
10use tracing::trace;
11
12use crate::metrics::{SERVER_BITCOIN_RPC_DURATION_SECONDS, SERVER_BITCOIN_RPC_REQUESTS_TOTAL};
13
14pub struct ServerBitcoinRpcTracked {
15    inner: DynServerBitcoinRpc,
16    name: &'static str,
17}
18
19impl std::fmt::Debug for ServerBitcoinRpcTracked {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("ServerBitcoinRpcTracked")
22            .field("name", &self.name)
23            .field("inner", &self.inner)
24            .finish()
25    }
26}
27
28impl ServerBitcoinRpcTracked {
29    pub fn new(inner: DynServerBitcoinRpc, name: &'static str) -> Self {
30        Self { inner, name }
31    }
32
33    fn record_call<T>(&self, method: &str, result: &Result<T>) {
34        let result_label = if result.is_ok() { "success" } else { "error" };
35        SERVER_BITCOIN_RPC_REQUESTS_TOTAL
36            .with_label_values(&[method, self.name, result_label])
37            .inc();
38    }
39}
40
41macro_rules! tracked_call {
42    ($self:ident, $method:expr, $call:expr) => {{
43        trace!(
44            target: LOG_BITCOIND,
45            method = $method,
46            name = $self.name,
47            "starting bitcoind rpc"
48        );
49        let start = now();
50        let timer = SERVER_BITCOIN_RPC_DURATION_SECONDS
51            .with_label_values(&[$method, $self.name])
52            .start_timer_ext();
53        let result = $call;
54        timer.observe_duration();
55        $self.record_call($method, &result);
56        let duration_ms = now()
57            .duration_since(start)
58            .unwrap_or_default()
59            .as_secs_f64()
60            * 1000.0;
61        trace!(
62            target: LOG_BITCOIND,
63            method = $method,
64            name = $self.name,
65            duration_ms,
66            error = %result.as_ref().map_err(|e| &**e).fmt_compact_result(),
67            "completed bitcoind rpc"
68        );
69        result
70    }};
71}
72
73#[async_trait::async_trait]
74impl IServerBitcoinRpc for ServerBitcoinRpcTracked {
75    fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
76        self.inner.get_bitcoin_rpc_config()
77    }
78
79    fn get_url(&self) -> SafeUrl {
80        self.inner.get_url()
81    }
82
83    async fn get_block_count(&self) -> Result<u64> {
84        tracked_call!(self, "get_block_count", self.inner.get_block_count().await)
85    }
86
87    async fn get_block_count_and_initial_block_download(&self) -> Result<(u64, bool)> {
88        tracked_call!(
89            self,
90            "get_block_count_and_initial_block_download",
91            self.inner
92                .get_block_count_and_initial_block_download()
93                .await
94        )
95    }
96
97    async fn get_block_hash(&self, height: u64) -> Result<BlockHash> {
98        tracked_call!(
99            self,
100            "get_block_hash",
101            self.inner.get_block_hash(height).await
102        )
103    }
104
105    async fn get_block(&self, block_hash: &BlockHash) -> Result<Block> {
106        tracked_call!(self, "get_block", self.inner.get_block(block_hash).await)
107    }
108
109    async fn get_feerate(&self) -> Result<Option<Feerate>> {
110        tracked_call!(self, "get_feerate", self.inner.get_feerate().await)
111    }
112
113    async fn submit_transaction(&self, transaction: Transaction) -> Result<()> {
114        tracked_call!(
115            self,
116            "submit_transaction",
117            self.inner.submit_transaction(transaction).await
118        )
119    }
120
121    async fn get_sync_progress(&self) -> Result<Option<f64>> {
122        tracked_call!(
123            self,
124            "get_sync_progress",
125            self.inner.get_sync_progress().await
126        )
127    }
128
129    async fn get_chain_id(&self) -> Result<ChainId> {
130        tracked_call!(self, "get_chain_id", self.inner.get_chain_id().await)
131    }
132}