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::{FmtCompactResultAnyhow 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.fmt_compact_result_anyhow(),
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_hash(&self, height: u64) -> Result<BlockHash> {
88        tracked_call!(
89            self,
90            "get_block_hash",
91            self.inner.get_block_hash(height).await
92        )
93    }
94
95    async fn get_block(&self, block_hash: &BlockHash) -> Result<Block> {
96        tracked_call!(self, "get_block", self.inner.get_block(block_hash).await)
97    }
98
99    async fn get_feerate(&self) -> Result<Option<Feerate>> {
100        tracked_call!(self, "get_feerate", self.inner.get_feerate().await)
101    }
102
103    async fn submit_transaction(&self, transaction: Transaction) -> Result<()> {
104        tracked_call!(
105            self,
106            "submit_transaction",
107            self.inner.submit_transaction(transaction).await
108        )
109    }
110
111    async fn get_sync_progress(&self) -> Result<Option<f64>> {
112        tracked_call!(
113            self,
114            "get_sync_progress",
115            self.inner.get_sync_progress().await
116        )
117    }
118
119    async fn get_chain_id(&self) -> Result<ChainId> {
120        tracked_call!(self, "get_chain_id", self.inner.get_chain_id().await)
121    }
122}