fedimint_server_bitcoin_rpc/
lib.rs1pub mod bitcoind;
2pub mod esplora;
3
4use anyhow::Result;
5use bitcoin::{BlockHash, Transaction};
6use fedimint_core::envs::BitcoinRpcConfig;
7use fedimint_core::util::{FmtCompactAnyhow, SafeUrl};
8use fedimint_core::{ChainId, Feerate};
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 "Initializing 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_block_count(&self) -> Result<u64> {
56 match self.bitcoind_client.get_block_count().await {
57 Ok(count) => Ok(count),
58 Err(e) => {
59 warn!(
60 target: LOG_SERVER,
61 error = %e.fmt_compact_anyhow(),
62 "BitcoindClient failed for get_block_count, falling back to EsploraClient"
63 );
64 self.esplora_client.get_block_count().await
65 }
66 }
67 }
68
69 async fn get_block_hash(&self, height: u64) -> Result<BlockHash> {
70 match self.bitcoind_client.get_block_hash(height).await {
71 Ok(hash) => Ok(hash),
72 Err(e) => {
73 warn!(
74 target: LOG_SERVER,
75 error = %e.fmt_compact_anyhow(),
76 height = height,
77 "BitcoindClient failed for get_block_hash, falling back to EsploraClient"
78 );
79 self.esplora_client.get_block_hash(height).await
80 }
81 }
82 }
83
84 async fn get_block(&self, block_hash: &BlockHash) -> Result<bitcoin::Block> {
85 match self.bitcoind_client.get_block(block_hash).await {
86 Ok(block) => Ok(block),
87 Err(e) => {
88 warn!(
89 target: LOG_SERVER,
90 error = %e.fmt_compact_anyhow(),
91 block_hash = %block_hash,
92 "BitcoindClient failed for get_block, falling back to EsploraClient"
93 );
94 self.esplora_client.get_block(block_hash).await
95 }
96 }
97 }
98
99 async fn get_feerate(&self) -> Result<Option<Feerate>> {
100 match self.bitcoind_client.get_feerate().await {
101 Ok(feerate) => Ok(feerate),
102 Err(e) => {
103 warn!(
104 target: LOG_SERVER,
105 error = %e.fmt_compact_anyhow(),
106 "BitcoindClient failed for get_feerate, falling back to EsploraClient"
107 );
108 self.esplora_client.get_feerate().await
109 }
110 }
111 }
112
113 async fn submit_transaction(&self, transaction: Transaction) {
114 self.bitcoind_client
117 .submit_transaction(transaction.clone())
118 .await;
119 self.esplora_client.submit_transaction(transaction).await;
120 }
121
122 async fn get_sync_progress(&self) -> Result<Option<f64>> {
123 self.esplora_client.get_sync_progress().await
125 }
126
127 async fn get_chain_id(&self) -> Result<ChainId> {
128 match self.bitcoind_client.get_chain_id().await {
129 Ok(chain_id) => Ok(chain_id),
130 Err(e) => {
131 warn!(
132 target: LOG_SERVER,
133 error = %e.fmt_compact_anyhow(),
134 "BitcoindClient failed for get_chain_id, falling back to EsploraClient"
135 );
136 self.esplora_client.get_chain_id().await
137 }
138 }
139 }
140}