fedimint_server_bitcoin_rpc/
bitcoind.rs1use anyhow::anyhow;
2use bitcoin::{BlockHash, Network, Transaction};
3use bitcoincore_rpc::Error::JsonRpc;
4use bitcoincore_rpc::bitcoincore_rpc_json::EstimateMode;
5use bitcoincore_rpc::jsonrpc::Error::Rpc;
6use bitcoincore_rpc::{Auth, Client, RpcApi};
7use fedimint_core::Feerate;
8use fedimint_core::envs::BitcoinRpcConfig;
9use fedimint_core::runtime::block_in_place;
10use fedimint_core::util::{FmtCompactAnyhow as _, SafeUrl};
11use fedimint_logging::{LOG_BITCOIND_CORE, LOG_SERVER};
12use fedimint_server_core::bitcoin_rpc::IServerBitcoinRpc;
13use tracing::{debug, info};
14
15#[derive(Debug)]
16pub struct BitcoindClient {
17 client: Client,
18 url: SafeUrl,
19}
20
21impl BitcoindClient {
22 pub fn new(username: String, password: String, url: &SafeUrl) -> anyhow::Result<Self> {
23 let auth = Auth::UserPass(username, password);
24
25 let url = url
26 .without_auth()
27 .map_err(|()| anyhow!("Failed to strip auth from Bitcoin Rpc Url"))?;
28
29 info!(
30 target: LOG_SERVER,
31 %url,
32 "Initiallizing bitcoin bitcoind backend"
33 );
34 Ok(Self {
35 client: Client::new(url.as_str(), auth)?,
36 url,
37 })
38 }
39}
40
41#[async_trait::async_trait]
42impl IServerBitcoinRpc for BitcoindClient {
43 fn get_bitcoin_rpc_config(&self) -> BitcoinRpcConfig {
44 BitcoinRpcConfig {
45 kind: "bitcoind".to_string(),
46 url: self.url.clone(),
47 }
48 }
49
50 fn get_url(&self) -> SafeUrl {
51 self.url.clone()
52 }
53
54 async fn get_network(&self) -> anyhow::Result<Network> {
55 block_in_place(|| self.client.get_blockchain_info())
56 .map(|network| network.chain)
57 .map_err(anyhow::Error::from)
58 .inspect_err(|err| {
59 debug!(
60 target: LOG_BITCOIND_CORE,
61 err = %err.fmt_compact_anyhow(),
62 "Error getting network from bitcoind");
63 })
64 }
65
66 async fn get_block_count(&self) -> anyhow::Result<u64> {
67 block_in_place(|| self.client.get_block_count())
69 .map(|height| height + 1)
70 .map_err(anyhow::Error::from)
71 }
72
73 async fn get_block_hash(&self, height: u64) -> anyhow::Result<BlockHash> {
74 block_in_place(|| self.client.get_block_hash(height)).map_err(anyhow::Error::from)
75 }
76
77 async fn get_block(&self, hash: &BlockHash) -> anyhow::Result<bitcoin::Block> {
78 block_in_place(|| self.client.get_block(hash)).map_err(anyhow::Error::from)
79 }
80
81 async fn get_feerate(&self) -> anyhow::Result<Option<Feerate>> {
82 let feerate = block_in_place(|| {
83 self.client
84 .estimate_smart_fee(1, Some(EstimateMode::Conservative))
85 })?
86 .fee_rate
87 .map(|per_kb| Feerate {
88 sats_per_kvb: per_kb.to_sat(),
89 });
90
91 Ok(feerate)
92 }
93
94 async fn submit_transaction(&self, transaction: Transaction) {
95 match block_in_place(|| self.client.send_raw_transaction(&transaction)) {
96 Err(JsonRpc(Rpc(e))) if e.code == -27 => (),
101 Err(e) => info!(target: LOG_BITCOIND_CORE, ?e, "Error broadcasting transaction"),
102 Ok(_) => (),
103 }
104 }
105
106 async fn get_sync_progress(&self) -> anyhow::Result<Option<f64>> {
107 Ok(Some(
108 block_in_place(|| self.client.get_blockchain_info())?.verification_progress,
109 ))
110 }
111}