fedimint_server_bitcoin_rpc/
lib.rs1pub 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 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}