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