fedimint_core/
lib.rs

1#![deny(clippy::pedantic, clippy::nursery)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::cast_possible_wrap)]
4#![allow(clippy::cast_precision_loss)]
5#![allow(clippy::cast_sign_loss)]
6#![allow(clippy::cognitive_complexity)]
7#![allow(clippy::doc_markdown)]
8#![allow(clippy::future_not_send)]
9#![allow(clippy::missing_const_for_fn)]
10#![allow(clippy::missing_errors_doc)]
11#![allow(clippy::missing_panics_doc)]
12#![allow(clippy::module_name_repetitions)]
13#![allow(clippy::must_use_candidate)]
14#![allow(clippy::redundant_pub_crate)]
15#![allow(clippy::return_self_not_must_use)]
16#![allow(clippy::similar_names)]
17#![allow(clippy::transmute_ptr_to_ptr)]
18#![allow(clippy::unsafe_derive_deserialize)]
19
20//! Fedimint Core library
21//!
22//! `fedimint-core` contains commonly used types, utilities and primitives,
23//! shared between both client and server code.
24//!
25//! Things that are server-side only typically live in `fedimint-server`, and
26//! client-side only in `fedimint-client`.
27//!
28//! ### Wasm support
29//!
30//! All code in `fedimint-core` needs to compile on Wasm, and `fedimint-core`
31//! includes helpers and wrappers around non-wasm-safe utitlies.
32//!
33//! In particular:
34//!
35//! * [`fedimint_core::task`] for task spawning and control
36//! * [`fedimint_core::time`] for time-related operations
37
38extern crate self as fedimint_core;
39
40use std::fmt::Debug;
41use std::io::Error;
42use std::str::FromStr;
43
44pub use amount::*;
45/// Mostly re-exported for [`Decodable`] macros.
46pub use anyhow;
47use bitcoin::address::NetworkUnchecked;
48pub use bitcoin::hashes::Hash as BitcoinHash;
49use bitcoin::{Address, Network};
50use lightning::util::ser::Writeable;
51use lightning_types::features::Bolt11InvoiceFeatures;
52pub use macro_rules_attribute::apply;
53pub use module::ServerModule;
54pub use peer_id::*;
55use serde::{Deserialize, Serialize};
56use thiserror::Error;
57pub use tiered::Tiered;
58pub use tiered_multi::*;
59pub use {hex, secp256k1};
60
61pub use crate::core::server;
62use crate::encoding::{Decodable, DecodeError, Encodable};
63use crate::module::registry::ModuleDecoderRegistry;
64
65/// Admin (guardian) client types
66pub mod admin_client;
67/// Bitcoin amount types
68mod amount;
69/// Federation-stored client backups
70pub mod backup;
71/// Legacy serde encoding for `bls12_381`
72pub mod bls12_381_serde;
73/// Federation configuration
74pub mod config;
75/// Fundamental types
76pub mod core;
77/// Database handling
78pub mod db;
79/// Consensus encoding
80pub mod encoding;
81pub mod endpoint_constants;
82/// Common environment variables
83pub mod envs;
84pub mod epoch;
85/// Formatting helpers
86pub mod fmt_utils;
87/// Federation invite code
88pub mod invite_code;
89/// Common macros
90#[macro_use]
91pub mod macros;
92/// Extenable module sysystem
93pub mod module;
94/// Peer networking
95pub mod net;
96/// `PeerId` type
97mod peer_id;
98/// Runtime (wasm32 vs native) differences handling
99pub mod runtime;
100/// Task handling, including wasm safe logic
101pub mod task;
102/// Types handling per-denomination values
103pub mod tiered;
104/// Types handling multiple per-denomination values
105pub mod tiered_multi;
106/// Time handling, wasm safe functionality
107pub mod time;
108/// Timing helpers
109pub mod timing;
110/// Fedimint transaction (inpus + outputs + signature) types
111pub mod transaction;
112/// Peg-in txo proofs
113pub mod txoproof;
114/// General purpose utilities
115pub mod util;
116/// Version
117pub mod version;
118
119/// Atomic BFT unit containing consensus items
120pub mod session_outcome;
121
122// It's necessary to wrap `hash_newtype!` in a module because the generated code
123// references a module called "core", but we export a conflicting module in this
124// file.
125mod txid {
126    use bitcoin::hashes::hash_newtype;
127    use bitcoin::hashes::sha256::Hash as Sha256;
128
129    hash_newtype!(
130        /// A transaction id for peg-ins, peg-outs and reissuances
131        pub struct TransactionId(Sha256);
132    );
133}
134pub use txid::TransactionId;
135
136/// Amount of bitcoin to send, or `All` to send all available funds
137#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone, Serialize, Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum BitcoinAmountOrAll {
140    All,
141    #[serde(untagged)]
142    Amount(#[serde(with = "bitcoin::amount::serde::as_sat")] bitcoin::Amount),
143}
144
145impl std::fmt::Display for BitcoinAmountOrAll {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        match self {
148            Self::All => write!(f, "all"),
149            Self::Amount(amount) => write!(f, "{amount}"),
150        }
151    }
152}
153
154impl FromStr for BitcoinAmountOrAll {
155    type Err = anyhow::Error;
156
157    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
158        if s == "all" {
159            Ok(Self::All)
160        } else {
161            let amount = Amount::from_str(s)?;
162            Ok(Self::Amount(amount.try_into()?))
163        }
164    }
165}
166
167/// `OutPoint` represents a globally unique output in a transaction
168///
169/// Hence, a transaction ID and the output index is required.
170#[derive(
171    Debug,
172    Clone,
173    Copy,
174    Eq,
175    PartialEq,
176    PartialOrd,
177    Ord,
178    Hash,
179    Deserialize,
180    Serialize,
181    Encodable,
182    Decodable,
183)]
184pub struct OutPoint {
185    /// The referenced transaction ID
186    pub txid: TransactionId,
187    /// As a transaction may have multiple outputs, this refers to the index of
188    /// the output in a transaction
189    pub out_idx: u64,
190}
191
192impl std::fmt::Display for OutPoint {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        write!(f, "{}:{}", self.txid, self.out_idx)
195    }
196}
197
198impl Encodable for TransactionId {
199    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
200        let bytes = &self[..];
201        writer.write_all(bytes)?;
202        Ok(bytes.len())
203    }
204}
205
206impl Decodable for TransactionId {
207    fn consensus_decode<D: std::io::Read>(
208        d: &mut D,
209        _modules: &ModuleDecoderRegistry,
210    ) -> Result<Self, DecodeError> {
211        let mut bytes = [0u8; 32];
212        d.read_exact(&mut bytes).map_err(DecodeError::from_err)?;
213        Ok(Self::from_byte_array(bytes))
214    }
215}
216
217#[derive(
218    Copy,
219    Clone,
220    Debug,
221    PartialEq,
222    Ord,
223    PartialOrd,
224    Eq,
225    Hash,
226    Serialize,
227    Deserialize,
228    Encodable,
229    Decodable,
230)]
231pub struct Feerate {
232    pub sats_per_kvb: u64,
233}
234
235impl Feerate {
236    pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
237        let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
238        bitcoin::Amount::from_sat(sats)
239    }
240}
241
242const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
243
244/// Converts weight to virtual bytes, defined in [BIP-141] as weight / 4
245/// (rounded up to the next integer).
246///
247/// [BIP-141]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations
248pub fn weight_to_vbytes(weight: u64) -> u64 {
249    (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR
250}
251
252#[derive(Debug, Error)]
253pub enum CoreError {
254    #[error("Mismatching outcome variant: expected {0}, got {1}")]
255    MismatchingVariant(&'static str, &'static str),
256}
257
258// Encode features for a bolt11 invoice without encoding the length.
259// This functionality was available in `lightning` v0.0.123, but has since been
260// removed. See the original code here:
261// https://docs.rs/lightning/0.0.123/src/lightning/ln/features.rs.html#745-750
262// https://docs.rs/lightning/0.0.123/src/lightning/ln/features.rs.html#1008-1012
263pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
264    let mut feature_bytes = vec![];
265    for f in features.le_flags().iter().rev() {
266        f.write(&mut feature_bytes)
267            .expect("Writing to byte vec can't fail");
268    }
269    feature_bytes
270}
271
272/// Outputs hex into an object implementing `fmt::Write`.
273///
274/// Vendored from `bitcoin_hashes` v0.11.0:
275/// <https://docs.rs/bitcoin_hashes/0.11.0/src/bitcoin_hashes/hex.rs.html#173-189>
276pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
277    let prec = f.precision().unwrap_or(2 * data.len());
278    let width = f.width().unwrap_or(2 * data.len());
279    for _ in (2 * data.len())..width {
280        f.write_str("0")?;
281    }
282    for ch in data.iter().take(prec / 2) {
283        write!(f, "{:02x}", *ch)?;
284    }
285    if prec < 2 * data.len() && prec % 2 == 1 {
286        write!(f, "{:x}", data[prec / 2] / 16)?;
287    }
288    Ok(())
289}
290
291/// Gets the (approximate) network from a bitcoin address.
292///
293/// This function mimics how `Address.network` is calculated in bitcoin v0.30.
294/// However, that field was removed in more recent versions in part because it
295/// can only distinguish between `Bitcoin`, `Testnet` and `Regtest`.
296///
297/// As of bitcoin v0.32.4, `Address::is_valid_for_network()` performs equality
298/// checks using `NetworkKind` and `KnownHrp`, which only distinguish between
299/// `Bitcoin`, `Testnet` and `Regtest`.
300/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/address/mod.rs.html#709-716>
301/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/network.rs.html#51-58>
302/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/address/mod.rs.html#200-209>
303pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
304    if address.is_valid_for_network(Network::Bitcoin) {
305        Network::Bitcoin
306    } else if address.is_valid_for_network(Network::Testnet) {
307        Network::Testnet
308    } else if address.is_valid_for_network(Network::Regtest) {
309        Network::Regtest
310    } else {
311        panic!("Address is not valid for any network");
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn converts_weight_to_vbytes() {
321        assert_eq!(1, weight_to_vbytes(4));
322        assert_eq!(2, weight_to_vbytes(5));
323    }
324
325    #[test]
326    fn calculate_fee() {
327        let feerate = Feerate { sats_per_kvb: 1000 };
328        assert_eq!(bitcoin::Amount::from_sat(25), feerate.calculate_fee(100));
329        assert_eq!(bitcoin::Amount::from_sat(26), feerate.calculate_fee(101));
330    }
331
332    #[test]
333    fn test_deserialize_amount_or_all() {
334        let all: BitcoinAmountOrAll = serde_json::from_str("\"all\"").unwrap();
335        assert_eq!(all, BitcoinAmountOrAll::All);
336
337        let amount: BitcoinAmountOrAll = serde_json::from_str("12345").unwrap();
338        assert_eq!(
339            amount,
340            BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(12345))
341        );
342
343        let all_string = all.to_string();
344        assert_eq!(all_string, "all");
345        let amount_string = amount.to_string();
346        assert_eq!(amount_string, "0.00012345 BTC");
347        let all_parsed = BitcoinAmountOrAll::from_str(&all_string).unwrap();
348        assert_eq!(all, all_parsed);
349        let amount_parsed = BitcoinAmountOrAll::from_str(&amount_string).unwrap();
350        assert_eq!(amount, amount_parsed);
351    }
352}