Skip to main content

fedimint_server_bitcoin_rpc/
lib.rs

1pub mod bitcoind;
2pub mod esplora;
3pub mod metrics;
4pub mod tracked;
5
6use anyhow::Result;
7use bitcoin::{BlockHash, Transaction};
8use fedimint_core::envs::BitcoinRpcConfig;
9use fedimint_core::util::{FmtCompactAnyhow, SafeUrl};
10use fedimint_core::{ChainId, Feerate};
11use fedimint_logging::LOG_SERVER;
12use fedimint_server_core::bitcoin_rpc::IServerBitcoinRpc;
13use tracing::warn;
14
15use crate::bitcoind::BitcoindClient;
16use crate::esplora::EsploraClient;
17
18#[derive(Debug)]
19pub struct BitcoindClientWithFallback {
20    bitcoind_client: BitcoindClient,
21    esplora_client: EsploraClient,
22}
23
24impl BitcoindClientWithFallback {
25    pub fn new(
26        username: String,
27        password: String,
28        bitcoind_url: &SafeUrl,
29        esplora_url: &SafeUrl,
30    ) -> Result<Self> {
31        warn!(
32            target: LOG_SERVER,
33            %bitcoind_url,
34            %esplora_url,
35            "Initializing bitcoin bitcoind backend with esplora fallback"
36        );
37        let bitcoind_client = BitcoindClient::new(username, password, bitcoind_url)?;
38        let esplora_client = EsploraClient::new(esplora_url)?;
39
40        Ok(Self {
41            bitcoind_client,
42            esplora_client,
43        })
44    }
45}
46
47#[async_trait::async_trait]
48impl IServerBitcoinRpc for BitcoindClientWithFallback {
49    fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
50        self.bitcoind_client.get_bitcoin_rpc_config()
51    }
52
53    fn get_url(&self) -> SafeUrl {
54        self.bitcoind_client.get_url()
55    }
56
57    async fn get_block_count(&self) -> Result<u64> {
58        match self.bitcoind_client.get_block_count().await {
59            Ok(count) => Ok(count),
60            Err(e) => {
61                warn!(
62                    target: LOG_SERVER,
63                    error = %e.fmt_compact_anyhow(),
64                    "BitcoindClient failed for get_block_count, falling back to EsploraClient"
65                );
66                self.esplora_client.get_block_count().await
67            }
68        }
69    }
70
71    async fn get_block_hash(&self, height: u64) -> Result<BlockHash> {
72        match self.bitcoind_client.get_block_hash(height).await {
73            Ok(hash) => Ok(hash),
74            Err(e) => {
75                warn!(
76                    target: LOG_SERVER,
77                    error = %e.fmt_compact_anyhow(),
78                    height = height,
79                    "BitcoindClient failed for get_block_hash, falling back to EsploraClient"
80                );
81                self.esplora_client.get_block_hash(height).await
82            }
83        }
84    }
85
86    async fn get_block(&self, block_hash: &BlockHash) -> Result<bitcoin::Block> {
87        match self.bitcoind_client.get_block(block_hash).await {
88            Ok(block) => Ok(block),
89            Err(e) => {
90                warn!(
91                    target: LOG_SERVER,
92                    error = %e.fmt_compact_anyhow(),
93                    block_hash = %block_hash,
94                    "BitcoindClient failed for get_block, falling back to EsploraClient"
95                );
96                self.esplora_client.get_block(block_hash).await
97            }
98        }
99    }
100
101    async fn get_feerate(&self) -> Result<Option<Feerate>> {
102        match self.bitcoind_client.get_feerate().await {
103            Ok(feerate) => Ok(feerate),
104            Err(e) => {
105                warn!(
106                    target: LOG_SERVER,
107                    error = %e.fmt_compact_anyhow(),
108                    "BitcoindClient failed for get_feerate, falling back to EsploraClient"
109                );
110                self.esplora_client.get_feerate().await
111            }
112        }
113    }
114
115    async fn submit_transaction(&self, transaction: Transaction) -> Result<()> {
116        match self
117            .bitcoind_client
118            .submit_transaction(transaction.clone())
119            .await
120        {
121            Ok(()) => Ok(()),
122            Err(e) => {
123                warn!(
124                    target: LOG_SERVER,
125                    error = %e.fmt_compact_anyhow(),
126                    "BitcoindClient failed for submit_transaction, falling back to EsploraClient"
127                );
128                self.esplora_client.submit_transaction(transaction).await
129            }
130        }
131    }
132
133    async fn get_sync_progress(&self) -> Result<Option<f64>> {
134        // We're always in sync, just like esplora
135        self.esplora_client.get_sync_progress().await
136    }
137
138    async fn get_chain_id(&self) -> Result<ChainId> {
139        match self.bitcoind_client.get_chain_id().await {
140            Ok(chain_id) => Ok(chain_id),
141            Err(e) => {
142                warn!(
143                    target: LOG_SERVER,
144                    error = %e.fmt_compact_anyhow(),
145                    "BitcoindClient failed for get_chain_id, falling back to EsploraClient"
146                );
147                self.esplora_client.get_chain_id().await
148            }
149        }
150    }
151}