fedimint_server_bitcoin_rpc/
tracked.rs1use 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}