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::needless_lifetimes)]
15#![allow(clippy::redundant_pub_crate)]
16#![allow(clippy::return_self_not_must_use)]
17#![allow(clippy::similar_names)]
18#![allow(clippy::transmute_ptr_to_ptr)]
19#![allow(clippy::unsafe_derive_deserialize)]
20
21//! Fedimint Core library
22//!
23//! `fedimint-core` contains commonly used types, utilities and primitives,
24//! shared between both client and server code.
25//!
26//! Things that are server-side only typically live in `fedimint-server`, and
27//! client-side only in `fedimint-client`.
28//!
29//! ### Wasm support
30//!
31//! All code in `fedimint-core` needs to compile on Wasm, and `fedimint-core`
32//! includes helpers and wrappers around non-wasm-safe utitlies.
33//!
34//! In particular:
35//!
36//! * [`fedimint_core::task`] for task spawning and control
37//! * [`fedimint_core::time`] for time-related operations
38
39extern crate self as fedimint_core;
40
41use std::fmt::{self, Debug};
42use std::io::Error;
43use std::ops::{self, Range};
44use std::str::FromStr;
45
46pub use amount::*;
47/// Mostly re-exported for [`Decodable`] macros.
48pub use anyhow;
49use bitcoin::address::NetworkUnchecked;
50pub use bitcoin::hashes::Hash as BitcoinHash;
51use bitcoin::{Address, Network};
52use envs::BitcoinRpcConfig;
53use lightning::util::ser::Writeable;
54use lightning_types::features::Bolt11InvoiceFeatures;
55pub use macro_rules_attribute::apply;
56pub use peer_id::*;
57use serde::{Deserialize, Serialize};
58use thiserror::Error;
59pub use tiered::Tiered;
60pub use tiered_multi::*;
61use util::SafeUrl;
62pub use {bitcoin, hex, secp256k1};
63
64use crate::encoding::{Decodable, DecodeError, Encodable};
65use crate::module::registry::ModuleDecoderRegistry;
66
67/// Admin (guardian) client types
68pub mod admin_client;
69/// Bitcoin amount types
70mod amount;
71/// Federation-stored client backups
72pub mod backup;
73/// Legacy serde encoding for `bls12_381`
74pub mod bls12_381_serde;
75/// Federation configuration
76pub mod config;
77/// Fundamental types
78pub mod core;
79/// Database handling
80pub mod db;
81/// Consensus encoding
82pub mod encoding;
83pub mod endpoint_constants;
84/// Common environment variables
85pub mod envs;
86pub mod epoch;
87/// Formatting helpers
88pub mod fmt_utils;
89/// Federation invite code
90pub mod invite_code;
91pub mod iroh_prod;
92pub mod log;
93/// Common macros
94#[macro_use]
95pub mod macros;
96/// Base 32 encoding
97pub mod base32;
98/// Extendable module sysystem
99pub mod module;
100/// Peer networking
101pub mod net;
102/// `PeerId` type
103mod peer_id;
104/// Runtime (wasm32 vs native) differences handling
105pub mod runtime;
106/// Rustls support
107pub mod rustls;
108/// Peer setup code for setup ceremony
109pub mod setup_code;
110/// Task handling, including wasm safe logic
111pub mod task;
112/// Types handling per-denomination values
113pub mod tiered;
114/// Types handling multiple per-denomination values
115pub mod tiered_multi;
116/// Time handling, wasm safe functionality
117pub mod time;
118/// Timing helpers
119pub mod timing;
120/// Fedimint transaction (inpus + outputs + signature) types
121pub mod transaction;
122/// Peg-in txo proofs
123pub mod txoproof;
124/// General purpose utilities
125pub mod util;
126/// Version
127pub mod version;
128
129/// Atomic BFT unit containing consensus items
130pub mod session_outcome;
131
132// It's necessary to wrap `hash_newtype!` in a module because the generated code
133// references a module called "core", but we export a conflicting module in this
134// file.
135mod txid {
136    use bitcoin::hashes::hash_newtype;
137    use bitcoin::hashes::sha256::Hash as Sha256;
138
139    hash_newtype!(
140        /// A transaction id for peg-ins, peg-outs and reissuances
141        pub struct TransactionId(Sha256);
142    );
143}
144pub use txid::TransactionId;
145
146/// Amount of bitcoin to send, or `All` to send all available funds
147#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone, Serialize, Deserialize)]
148#[serde(rename_all = "snake_case")]
149pub enum BitcoinAmountOrAll {
150    All,
151    #[serde(untagged)]
152    Amount(#[serde(with = "bitcoin::amount::serde::as_sat")] bitcoin::Amount),
153}
154
155impl std::fmt::Display for BitcoinAmountOrAll {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        match self {
158            Self::All => write!(f, "all"),
159            Self::Amount(amount) => write!(f, "{amount}"),
160        }
161    }
162}
163
164impl FromStr for BitcoinAmountOrAll {
165    type Err = anyhow::Error;
166
167    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
168        if s == "all" {
169            Ok(Self::All)
170        } else {
171            let amount = Amount::from_str(s)?;
172            Ok(Self::Amount(amount.try_into()?))
173        }
174    }
175}
176
177/// `InPoint` represents a globally unique input in a transaction
178///
179/// Hence, a transaction ID and the input index is required.
180#[derive(
181    Debug,
182    Clone,
183    Copy,
184    Eq,
185    PartialEq,
186    PartialOrd,
187    Ord,
188    Hash,
189    Deserialize,
190    Serialize,
191    Encodable,
192    Decodable,
193)]
194pub struct InPoint {
195    /// The referenced transaction ID
196    pub txid: TransactionId,
197    /// As a transaction may have multiple inputs, this refers to the index of
198    /// the input in a transaction
199    pub in_idx: u64,
200}
201
202impl std::fmt::Display for InPoint {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(f, "{}:{}", self.txid, self.in_idx)
205    }
206}
207
208/// `OutPoint` represents a globally unique output in a transaction
209///
210/// Hence, a transaction ID and the output index is required.
211#[derive(
212    Debug,
213    Clone,
214    Copy,
215    Eq,
216    PartialEq,
217    PartialOrd,
218    Ord,
219    Hash,
220    Deserialize,
221    Serialize,
222    Encodable,
223    Decodable,
224)]
225pub struct OutPoint {
226    /// The referenced transaction ID
227    pub txid: TransactionId,
228    /// As a transaction may have multiple outputs, this refers to the index of
229    /// the output in a transaction
230    pub out_idx: u64,
231}
232
233impl std::fmt::Display for OutPoint {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        write!(f, "{}:{}", self.txid, self.out_idx)
236    }
237}
238
239/// A contiguous range of input/output indexes
240#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
241pub struct IdxRange {
242    start: u64,
243    end: u64,
244}
245
246impl IdxRange {
247    pub fn new_single(start: u64) -> Option<Self> {
248        start.checked_add(1).map(|end| Self { start, end })
249    }
250
251    pub fn start(self) -> u64 {
252        self.start
253    }
254
255    pub fn count(self) -> usize {
256        self.into_iter().count()
257    }
258
259    pub fn from_inclusive(range: ops::RangeInclusive<u64>) -> Option<Self> {
260        range.end().checked_add(1).map(|end| Self {
261            start: *range.start(),
262            end,
263        })
264    }
265}
266
267impl From<Range<u64>> for IdxRange {
268    fn from(Range { start, end }: Range<u64>) -> Self {
269        Self { start, end }
270    }
271}
272
273impl IntoIterator for IdxRange {
274    type Item = u64;
275    type IntoIter = ops::Range<u64>;
276
277    fn into_iter(self) -> Self::IntoIter {
278        ops::Range {
279            start: self.start,
280            end: self.end,
281        }
282    }
283}
284
285/// Represents a range of output indices for a single transaction
286#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
287pub struct OutPointRange {
288    pub txid: TransactionId,
289    idx_range: IdxRange,
290}
291
292impl OutPointRange {
293    pub fn new(txid: TransactionId, idx_range: IdxRange) -> Self {
294        Self { txid, idx_range }
295    }
296
297    pub fn new_single(txid: TransactionId, idx: u64) -> Option<Self> {
298        IdxRange::new_single(idx).map(|idx_range| Self { txid, idx_range })
299    }
300
301    pub fn start_idx(self) -> u64 {
302        self.idx_range.start()
303    }
304
305    pub fn out_idx_iter(self) -> impl Iterator<Item = u64> {
306        self.idx_range.into_iter()
307    }
308
309    pub fn count(self) -> usize {
310        self.idx_range.count()
311    }
312
313    pub fn txid(&self) -> TransactionId {
314        self.txid
315    }
316}
317
318impl IntoIterator for OutPointRange {
319    type Item = OutPoint;
320    type IntoIter = OutPointRangeIter;
321
322    fn into_iter(self) -> Self::IntoIter {
323        OutPointRangeIter {
324            txid: self.txid,
325            inner: self.idx_range.into_iter(),
326        }
327    }
328}
329
330pub struct OutPointRangeIter {
331    txid: TransactionId,
332    inner: ops::Range<u64>,
333}
334
335impl Iterator for OutPointRangeIter {
336    type Item = OutPoint;
337
338    fn next(&mut self) -> Option<Self::Item> {
339        self.inner.next().map(|idx| OutPoint {
340            txid: self.txid,
341            out_idx: idx,
342        })
343    }
344}
345
346impl Encodable for TransactionId {
347    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
348        let bytes = &self[..];
349        writer.write_all(bytes)?;
350        Ok(())
351    }
352}
353
354impl Decodable for TransactionId {
355    fn consensus_decode_partial<D: std::io::Read>(
356        d: &mut D,
357        _modules: &ModuleDecoderRegistry,
358    ) -> Result<Self, DecodeError> {
359        let mut bytes = [0u8; 32];
360        d.read_exact(&mut bytes).map_err(DecodeError::from_err)?;
361        Ok(Self::from_byte_array(bytes))
362    }
363}
364
365#[derive(
366    Copy,
367    Clone,
368    Debug,
369    PartialEq,
370    Ord,
371    PartialOrd,
372    Eq,
373    Hash,
374    Serialize,
375    Deserialize,
376    Encodable,
377    Decodable,
378)]
379pub struct Feerate {
380    pub sats_per_kvb: u64,
381}
382
383impl fmt::Display for Feerate {
384    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385        f.write_fmt(format_args!("{}sat/kvb", self.sats_per_kvb))
386    }
387}
388
389impl Feerate {
390    pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
391        let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
392        bitcoin::Amount::from_sat(sats)
393    }
394}
395
396const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
397
398/// Converts weight to virtual bytes, defined in [BIP-141] as weight / 4
399/// (rounded up to the next integer).
400///
401/// [BIP-141]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations
402pub fn weight_to_vbytes(weight: u64) -> u64 {
403    weight.div_ceil(WITNESS_SCALE_FACTOR)
404}
405
406#[derive(Debug, Error)]
407pub enum CoreError {
408    #[error("Mismatching outcome variant: expected {0}, got {1}")]
409    MismatchingVariant(&'static str, &'static str),
410}
411
412// Encode features for a bolt11 invoice without encoding the length.
413// This functionality was available in `lightning` v0.0.123, but has since been
414// removed. See the original code here:
415// https://docs.rs/lightning/0.0.123/src/lightning/ln/features.rs.html#745-750
416// https://docs.rs/lightning/0.0.123/src/lightning/ln/features.rs.html#1008-1012
417pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
418    let mut feature_bytes = vec![];
419    for f in features.le_flags().iter().rev() {
420        f.write(&mut feature_bytes)
421            .expect("Writing to byte vec can't fail");
422    }
423    feature_bytes
424}
425
426/// Outputs hex into an object implementing `fmt::Write`.
427///
428/// Vendored from `bitcoin_hashes` v0.11.0:
429/// <https://docs.rs/bitcoin_hashes/0.11.0/src/bitcoin_hashes/hex.rs.html#173-189>
430pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
431    let prec = f.precision().unwrap_or(2 * data.len());
432    let width = f.width().unwrap_or(2 * data.len());
433    for _ in (2 * data.len())..width {
434        f.write_str("0")?;
435    }
436    for ch in data.iter().take(prec / 2) {
437        write!(f, "{:02x}", *ch)?;
438    }
439    if prec < 2 * data.len() && prec % 2 == 1 {
440        write!(f, "{:x}", data[prec / 2] / 16)?;
441    }
442    Ok(())
443}
444
445/// Gets the (approximate) network from a bitcoin address.
446///
447/// This function mimics how `Address.network` is calculated in bitcoin v0.30.
448/// However, that field was removed in more recent versions in part because it
449/// can only distinguish between `Bitcoin`, `Testnet` and `Regtest`.
450///
451/// As of bitcoin v0.32.4, `Address::is_valid_for_network()` performs equality
452/// checks using `NetworkKind` and `KnownHrp`, which only distinguish between
453/// `Bitcoin`, `Testnet` and `Regtest`.
454/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/address/mod.rs.html#709-716>
455/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/network.rs.html#51-58>
456/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/address/mod.rs.html#200-209>
457pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
458    if address.is_valid_for_network(Network::Bitcoin) {
459        Network::Bitcoin
460    } else if address.is_valid_for_network(Network::Testnet) {
461        Network::Testnet
462    } else if address.is_valid_for_network(Network::Regtest) {
463        Network::Regtest
464    } else {
465        panic!("Address is not valid for any network");
466    }
467}
468
469/// Returns the default esplora server according to the network
470pub fn default_esplora_server(network: Network, port: Option<String>) -> BitcoinRpcConfig {
471    BitcoinRpcConfig {
472        kind: "esplora".to_string(),
473        url: match network {
474            Network::Bitcoin => SafeUrl::parse("https://mempool.space/api/"),
475            Network::Testnet => SafeUrl::parse("https://mempool.space/testnet/api/"),
476            Network::Testnet4 => SafeUrl::parse("https://mempool.space/testnet4/api/"),
477            Network::Signet => SafeUrl::parse("https://mutinynet.com/api/"),
478            Network::Regtest => SafeUrl::parse(&format!(
479                "http://127.0.0.1:{}/",
480                port.unwrap_or_else(|| String::from("50002"))
481            )),
482            _ => panic!("Failed to parse default esplora server"),
483        }
484        .expect("Failed to parse default esplora server"),
485    }
486}
487
488#[cfg(test)]
489mod tests;