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 {:?} but esplora reported {:?}",
65 bitcoind_network, esplora_network
66 );
67 }
68 Ok(bitcoind_network)
69 }
70 Err(e) => {
71 warn!(
72 target: LOG_SERVER,
73 error = %e.fmt_compact_anyhow(),
74 "BitcoindClient failed for get_network, falling back to EsploraClient"
75 );
76
77 self.esplora_client.get_network().await
78 }
79 }
80 }
81
82 async fn get_block_count(&self) -> Result<u64> {
83 match self.bitcoind_client.get_block_count().await {
84 Ok(count) => Ok(count),
85 Err(e) => {
86 warn!(
87 target: LOG_SERVER,
88 error = %e.fmt_compact_anyhow(),
89 "BitcoindClient failed for get_block_count, falling back to EsploraClient"
90 );
91 self.esplora_client.get_block_count().await
92 }
93 }
94 }
95
96 async fn get_block_hash(&self, height: u64) -> Result<BlockHash> {
97 match self.bitcoind_client.get_block_hash(height).await {
98 Ok(hash) => Ok(hash),
99 Err(e) => {
100 warn!(
101 target: LOG_SERVER,
102 error = %e.fmt_compact_anyhow(),
103 height = height,
104 "BitcoindClient failed for get_block_hash, falling back to EsploraClient"
105 );
106 self.esplora_client.get_block_hash(height).await
107 }
108 }
109 }
110
111 async fn get_block(&self, block_hash: &BlockHash) -> Result<bitcoin::Block> {
112 match self.bitcoind_client.get_block(block_hash).await {
113 Ok(block) => Ok(block),
114 Err(e) => {
115 warn!(
116 target: LOG_SERVER,
117 error = %e.fmt_compact_anyhow(),
118 block_hash = %block_hash,
119 "BitcoindClient failed for get_block, falling back to EsploraClient"
120 );
121 self.esplora_client.get_block(block_hash).await
122 }
123 }
124 }
125
126 async fn get_feerate(&self) -> Result<Option<Feerate>> {
127 match self.bitcoind_client.get_feerate().await {
128 Ok(feerate) => Ok(feerate),
129 Err(e) => {
130 warn!(
131 target: LOG_SERVER,
132 error = %e.fmt_compact_anyhow(),
133 "BitcoindClient failed for get_feerate, falling back to EsploraClient"
134 );
135 self.esplora_client.get_feerate().await
136 }
137 }
138 }
139
140 async fn submit_transaction(&self, transaction: Transaction) {
141 self.bitcoind_client
144 .submit_transaction(transaction.clone())
145 .await;
146 self.esplora_client.submit_transaction(transaction).await;
147 }
148
149 async fn get_sync_percentage(&self) -> Result<Option<f64>> {
150 self.esplora_client.get_sync_percentage().await
152 }
153}