fedimint_server_bitcoin_rpc/
lib.rs1pub 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(
24 username: String,
25 password: String,
26 bitcoind_url: &SafeUrl,
27 esplora_url: &SafeUrl,
28 ) -> Result<Self> {
29 warn!(
30 target: LOG_SERVER,
31 %bitcoind_url,
32 %esplora_url,
33 "Initiallizing bitcoin bitcoind backend with esplora fallback"
34 );
35 let bitcoind_client = BitcoindClient::new(username, password, bitcoind_url)?;
36 let esplora_client = EsploraClient::new(esplora_url)?;
37
38 Ok(Self {
39 bitcoind_client,
40 esplora_client,
41 })
42 }
43}
44
45#[async_trait::async_trait]
46impl IServerBitcoinRpc for BitcoindClientWithFallback {
47 fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
48 self.bitcoind_client.get_bitcoin_rpc_config()
49 }
50
51 fn get_url(&self) -> SafeUrl {
52 self.bitcoind_client.get_url()
53 }
54
55 async fn get_network(&self) -> Result<Network> {
56 match self.bitcoind_client.get_network().await {
57 Ok(bitcoind_network) => {
58 if let Ok(esplora_network) = self.esplora_client.get_network().await {
62 assert_eq!(
63 bitcoind_network, esplora_network,
64 "Network mismatch: bitcoind reported {bitcoind_network:?} but esplora reported {esplora_network:?}",
65 );
66 }
67 Ok(bitcoind_network)
68 }
69 Err(e) => {
70 warn!(
71 target: LOG_SERVER,
72 error = %e.fmt_compact_anyhow(),
73 "BitcoindClient failed for get_network, falling back to EsploraClient"
74 );
75
76 self.esplora_client.get_network().await
77 }
78 }
79 }
80
81 async fn get_block_count(&self) -> Result<u64> {
82 match self.bitcoind_client.get_block_count().await {
83 Ok(count) => Ok(count),
84 Err(e) => {
85 warn!(
86 target: LOG_SERVER,
87 error = %e.fmt_compact_anyhow(),
88 "BitcoindClient failed for get_block_count, falling back to EsploraClient"
89 );
90 self.esplora_client.get_block_count().await
91 }
92 }
93 }
94
95 async fn get_block_hash(&self, height: u64) -> Result<BlockHash> {
96 match self.bitcoind_client.get_block_hash(height).await {
97 Ok(hash) => Ok(hash),
98 Err(e) => {
99 warn!(
100 target: LOG_SERVER,
101 error = %e.fmt_compact_anyhow(),
102 height = height,
103 "BitcoindClient failed for get_block_hash, falling back to EsploraClient"
104 );
105 self.esplora_client.get_block_hash(height).await
106 }
107 }
108 }
109
110 async fn get_block(&self, block_hash: &BlockHash) -> Result<bitcoin::Block> {
111 match self.bitcoind_client.get_block(block_hash).await {
112 Ok(block) => Ok(block),
113 Err(e) => {
114 warn!(
115 target: LOG_SERVER,
116 error = %e.fmt_compact_anyhow(),
117 block_hash = %block_hash,
118 "BitcoindClient failed for get_block, falling back to EsploraClient"
119 );
120 self.esplora_client.get_block(block_hash).await
121 }
122 }
123 }
124
125 async fn get_feerate(&self) -> Result<Option<Feerate>> {
126 match self.bitcoind_client.get_feerate().await {
127 Ok(feerate) => Ok(feerate),
128 Err(e) => {
129 warn!(
130 target: LOG_SERVER,
131 error = %e.fmt_compact_anyhow(),
132 "BitcoindClient failed for get_feerate, falling back to EsploraClient"
133 );
134 self.esplora_client.get_feerate().await
135 }
136 }
137 }
138
139 async fn submit_transaction(&self, transaction: Transaction) {
140 self.bitcoind_client
143 .submit_transaction(transaction.clone())
144 .await;
145 self.esplora_client.submit_transaction(transaction).await;
146 }
147
148 async fn get_sync_progress(&self) -> Result<Option<f64>> {
149 self.esplora_client.get_sync_progress().await
151 }
152}