fedimint_bitcoind/
shared.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use fedimint_core::Feerate;
5use fedimint_core::task::TaskGroup;
6use fedimint_core::util::SafeUrl;
7use fedimint_logging::LOG_BITCOIN;
8use fedimint_server_core::ServerModuleShared;
9use tokio::sync::{Mutex, watch};
10use tracing::debug;
11
12/// Used for estimating a feerate that will confirm within a target number of
13/// blocks.
14///
15/// Since the wallet's UTXOs are a shared resource, we need to reduce the risk
16/// of a peg-out transaction getting stuck in the mempool, hence we use a low
17/// confirmation target. Other fee bumping techniques, such as RBF and CPFP, can
18/// help mitigate this problem but are out-of-scope for this version of the
19/// wallet.
20pub const CONFIRMATION_TARGET: u16 = 1;
21
22use crate::DynBitcoindRpc;
23type SharedKey = (bitcoin::Network, SafeUrl);
24
25/// A (potentially) shared (between server modules) bitcoin utility trait
26///
27/// Multiple Fedimint modules might want same Bitcoin-network facilities
28/// like monitoring the feerate or block count, and each of them doing the same
29/// thing does not scale very well.
30///
31/// This type allows deduplicating the work, by sharing it via
32/// `ServerModuleInitArgs::shared`.
33///
34/// In theory different modules might be configured to use different Bitcoin
35/// network/bitcoin sources, so shared facilities here are keyed over the the
36/// Bitcoin rpc url to use.
37struct ServerModuleSharedBitcoinInner {
38    task_group: TaskGroup,
39    feerate_rx: tokio::sync::Mutex<BTreeMap<SharedKey, watch::Receiver<Option<Feerate>>>>,
40    block_count_rx: tokio::sync::Mutex<BTreeMap<SharedKey, watch::Receiver<Option<u64>>>>,
41}
42
43impl ServerModuleSharedBitcoinInner {
44    pub async fn feerate_receiver(
45        &self,
46        network: bitcoin::Network,
47        btc_rpc: DynBitcoindRpc,
48    ) -> anyhow::Result<watch::Receiver<Option<Feerate>>> {
49        let key = (network, btc_rpc.get_bitcoin_rpc_config().url);
50        let mut write = self.feerate_rx.lock().await;
51
52        if let Some(v) = write.get(&key) {
53            return Ok(v.clone());
54        }
55
56        let (tx, rx) = watch::channel(None);
57
58        btc_rpc
59            .clone()
60            .spawn_fee_rate_update_task(&self.task_group, network, 1, {
61                move |feerate| {
62                    debug!(target: LOG_BITCOIN, %feerate, "New feerate");
63                    tx.send_replace(Some(feerate));
64                }
65            })?;
66
67        write.insert(key, rx.clone());
68
69        Ok(rx)
70    }
71    pub async fn block_count_receiver(
72        &self,
73        network: bitcoin::Network,
74        btc_rpc: DynBitcoindRpc,
75    ) -> watch::Receiver<Option<u64>> {
76        let key = (network, btc_rpc.get_bitcoin_rpc_config().url);
77        let mut write = self.block_count_rx.lock().await;
78
79        if let Some(v) = write.get(&key) {
80            return v.clone();
81        }
82
83        let (tx, rx) = watch::channel(None);
84
85        btc_rpc
86            .clone()
87            .spawn_block_count_update_task(&self.task_group, {
88                move |block_count| {
89                    debug!(target: LOG_BITCOIN, %block_count, "New block count");
90                    tx.send_replace(Some(block_count));
91                }
92            });
93
94        write.insert(key, rx.clone());
95
96        rx
97    }
98}
99
100#[derive(Clone)]
101pub struct ServerModuleSharedBitcoin {
102    inner: Arc<ServerModuleSharedBitcoinInner>,
103}
104
105impl ServerModuleSharedBitcoin {
106    pub async fn feerate_receiver(
107        &self,
108        network: bitcoin::Network,
109        btc_rpc: DynBitcoindRpc,
110    ) -> anyhow::Result<watch::Receiver<Option<Feerate>>> {
111        self.inner.feerate_receiver(network, btc_rpc).await
112    }
113    pub async fn block_count_receiver(
114        &self,
115        network: bitcoin::Network,
116        btc_rpc: DynBitcoindRpc,
117    ) -> watch::Receiver<Option<u64>> {
118        self.inner.block_count_receiver(network, btc_rpc).await
119    }
120}
121impl ServerModuleShared for ServerModuleSharedBitcoin {
122    fn new(task_group: TaskGroup) -> Self {
123        Self {
124            inner: Arc::new(ServerModuleSharedBitcoinInner {
125                task_group,
126                feerate_rx: Mutex::default(),
127                block_count_rx: Mutex::default(),
128            }),
129        }
130    }
131}