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(bitcoind_url: &SafeUrl, esplora_url: &SafeUrl) -> Result<Self> {
24 let bitcoind_client = BitcoindClient::new(bitcoind_url)?;
25 let esplora_client = EsploraClient::new(esplora_url)?;
26
27 Ok(Self {
28 bitcoind_client,
29 esplora_client,
30 })
31 }
32}
33
34#[async_trait::async_trait]
35impl IServerBitcoinRpc for BitcoindClientWithFallback {
36 fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
37 self.bitcoind_client.get_bitcoin_rpc_config()
38 }
39
40 fn get_url(&self) -> SafeUrl {
41 self.bitcoind_client.get_url()
42 }
43
44 async fn get_network(&self) -> Result<Network> {
45 match self.bitcoind_client.get_network().await {
46 Ok(network) => Ok(network),
47 Err(e) => {
48 warn!(
49 target: LOG_SERVER,
50 error = %e.fmt_compact_anyhow(),
51 "BitcoindClient failed for get_network, falling back to EsploraClient"
52 );
53 self.esplora_client.get_network().await
54 }
55 }
56 }
57
58 async fn get_block_count(&self) -> Result<u64> {
59 match self.bitcoind_client.get_block_count().await {
60 Ok(count) => Ok(count),
61 Err(e) => {
62 warn!(
63 target: LOG_SERVER,
64 error = %e.fmt_compact_anyhow(),
65 "BitcoindClient failed for get_block_count, falling back to EsploraClient"
66 );
67 self.esplora_client.get_block_count().await
68 }
69 }
70 }
71
72 async fn get_block_hash(&self, height: u64) -> Result<BlockHash> {
73 match self.bitcoind_client.get_block_hash(height).await {
74 Ok(hash) => Ok(hash),
75 Err(e) => {
76 warn!(
77 target: LOG_SERVER,
78 error = %e.fmt_compact_anyhow(),
79 height = height,
80 "BitcoindClient failed for get_block_hash, falling back to EsploraClient"
81 );
82 self.esplora_client.get_block_hash(height).await
83 }
84 }
85 }
86
87 async fn get_block(&self, block_hash: &BlockHash) -> Result<bitcoin::Block> {
88 match self.bitcoind_client.get_block(block_hash).await {
89 Ok(block) => Ok(block),
90 Err(e) => {
91 warn!(
92 target: LOG_SERVER,
93 error = %e.fmt_compact_anyhow(),
94 block_hash = %block_hash,
95 "BitcoindClient failed for get_block, falling back to EsploraClient"
96 );
97 self.esplora_client.get_block(block_hash).await
98 }
99 }
100 }
101
102 async fn get_feerate(&self) -> Result<Option<Feerate>> {
103 match self.bitcoind_client.get_feerate().await {
104 Ok(feerate) => Ok(feerate),
105 Err(e) => {
106 warn!(
107 target: LOG_SERVER,
108 error = %e.fmt_compact_anyhow(),
109 "BitcoindClient failed for get_feerate, falling back to EsploraClient"
110 );
111 self.esplora_client.get_feerate().await
112 }
113 }
114 }
115
116 async fn submit_transaction(&self, transaction: Transaction) {
117 self.bitcoind_client
120 .submit_transaction(transaction.clone())
121 .await;
122 self.esplora_client.submit_transaction(transaction).await;
123 }
124
125 async fn get_sync_percentage(&self) -> Result<Option<f64>> {
126 self.esplora_client.get_sync_percentage().await
128 }
129}