Skip to main content

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::*;
47use bitcoin::address::NetworkUnchecked;
48pub use bitcoin::hashes::Hash as BitcoinHash;
49use bitcoin::{Address, Network};
50use envs::BitcoinRpcConfig;
51use lightning::util::ser::Writeable;
52use lightning_types::features::Bolt11InvoiceFeatures;
53pub use macro_rules_attribute::apply;
54pub use peer_id::*;
55use serde::{Deserialize, Deserializer, Serialize, Serializer};
56use thiserror::Error;
57pub use tiered::Tiered;
58pub use tiered_multi::*;
59use util::SafeUrl;
60pub use {bitcoin, hex, secp256k1};
61
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;
84/// Formatting helpers
85pub mod fmt_utils;
86/// Federation invite code
87pub mod invite_code;
88pub mod log;
89/// Common macros
90#[macro_use]
91pub mod macros;
92/// Base 32 encoding
93pub mod base32;
94/// Extendable module sysystem
95pub mod module;
96/// Peer networking
97pub mod net;
98pub mod peer_availability;
99/// `PeerId` type
100mod peer_id;
101/// Runtime (wasm32 vs native) differences handling
102pub mod runtime;
103/// Rustls support
104pub mod rustls;
105/// Peer setup code for setup ceremony
106pub mod setup_code;
107/// Task handling, including wasm safe logic
108pub mod task;
109/// Types handling per-denomination values
110pub mod tiered;
111/// Types handling multiple per-denomination values
112pub mod tiered_multi;
113/// Time handling, wasm safe functionality
114pub mod time;
115/// Timing helpers
116pub mod timing;
117/// Fedimint transaction (inpus + outputs + signature) types
118pub mod transaction;
119/// Peg-in txo proofs
120pub mod txoproof;
121/// General purpose utilities
122pub mod util;
123/// Version
124pub mod version;
125
126/// Atomic BFT unit containing consensus items
127pub mod session_outcome;
128
129// It's necessary to wrap `hash_newtype!` in a module because the generated code
130// references a module called "core", but we export a conflicting module in this
131// file.
132mod txid;
133pub use txid::TransactionId;
134
135pub struct TransactionIdShortFmt<'a>(&'a TransactionId);
136pub struct TransactionIdFullFmt<'a>(&'a TransactionId);
137
138impl TransactionId {
139    pub fn fmt_short(&self) -> TransactionIdShortFmt<'_> {
140        TransactionIdShortFmt(self)
141    }
142
143    pub fn fmt_full(&self) -> TransactionIdFullFmt<'_> {
144        TransactionIdFullFmt(self)
145    }
146}
147
148impl std::fmt::Display for TransactionIdShortFmt<'_> {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        let bytes = &self.0[..];
151        format_hex(&bytes[..4], f)?;
152        f.write_str("_")?;
153        format_hex(&bytes[28..], f)
154    }
155}
156
157impl std::fmt::Display for TransactionIdFullFmt<'_> {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        let bytes = &self.0[..];
160        format_hex(bytes, f)
161    }
162}
163
164/// Bitcoin chain identifier
165///
166/// This is a newtype wrapper around [`bitcoin::BlockHash`] representing the
167/// block hash at height 1, which uniquely identifies a Bitcoin chain (mainnet,
168/// testnet, signet, regtest, or custom networks), unlike genesis block hash
169/// which is often the same for same types of networks (e.g. mutinynet vs
170/// signet4).
171///
172/// Using a distinct type instead of raw `BlockHash` provides type safety and
173/// makes the intent clearer when passing chain identifiers through APIs.
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)]
175pub struct ChainId(pub bitcoin::BlockHash);
176
177impl ChainId {
178    /// Create a new `ChainId` from a `BlockHash`
179    pub fn new(block_hash: bitcoin::BlockHash) -> Self {
180        Self(block_hash)
181    }
182
183    /// Get the inner `BlockHash`
184    pub fn block_hash(&self) -> bitcoin::BlockHash {
185        self.0
186    }
187}
188
189impl From<bitcoin::BlockHash> for ChainId {
190    fn from(block_hash: bitcoin::BlockHash) -> Self {
191        Self(block_hash)
192    }
193}
194
195impl From<ChainId> for bitcoin::BlockHash {
196    fn from(chain_id: ChainId) -> Self {
197        chain_id.0
198    }
199}
200
201impl std::fmt::Display for ChainId {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        write!(f, "{}", self.0)
204    }
205}
206
207impl FromStr for ChainId {
208    type Err = bitcoin::hashes::hex::HexToArrayError;
209
210    fn from_str(s: &str) -> Result<Self, Self::Err> {
211        bitcoin::BlockHash::from_str(s).map(Self)
212    }
213}
214
215impl Serialize for ChainId {
216    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
217    where
218        S: Serializer,
219    {
220        self.0.serialize(serializer)
221    }
222}
223
224impl<'de> Deserialize<'de> for ChainId {
225    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
226    where
227        D: Deserializer<'de>,
228    {
229        bitcoin::BlockHash::deserialize(deserializer).map(Self)
230    }
231}
232
233/// Amount of bitcoin to send, or `All` to send all available funds
234#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone)]
235pub enum BitcoinAmountOrAll {
236    All,
237    Amount(bitcoin::Amount),
238}
239
240impl std::fmt::Display for BitcoinAmountOrAll {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        match self {
243            Self::All => write!(f, "all"),
244            Self::Amount(amount) => write!(f, "{amount}"),
245        }
246    }
247}
248
249impl FromStr for BitcoinAmountOrAll {
250    type Err = ParseBitcoinAmountOrAllError;
251
252    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
253        if s.eq_ignore_ascii_case("all") {
254            Ok(Self::All)
255        } else {
256            let amount = Amount::from_str(s)?;
257            Ok(Self::Amount(amount.try_into()?))
258        }
259    }
260}
261
262/// Failure to parse a [`BitcoinAmountOrAll`] from its string form.
263#[derive(Debug, Error)]
264#[non_exhaustive]
265pub enum ParseBitcoinAmountOrAllError {
266    /// The string is neither `all` nor a valid amount.
267    #[error("Invalid amount: {0}")]
268    Amount(#[from] ParseAmountError),
269    /// The amount is valid but has sub-satoshi precision.
270    #[error("Amount cannot be expressed in satoshis: {0}")]
271    Precision(#[from] AmountConversionError),
272}
273
274// Custom serde to handle both "all" and numbers/strings
275impl<'de> Deserialize<'de> for BitcoinAmountOrAll {
276    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
277    where
278        D: Deserializer<'de>,
279    {
280        use serde::de::Error;
281
282        struct Visitor;
283
284        impl serde::de::Visitor<'_> for Visitor {
285            type Value = BitcoinAmountOrAll;
286
287            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
288                write!(f, "a bitcoin amount as number or 'all'")
289            }
290
291            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
292            where
293                E: Error,
294            {
295                if v.eq_ignore_ascii_case("all") {
296                    Ok(BitcoinAmountOrAll::All)
297                } else {
298                    let sat: u64 = v.parse().map_err(E::custom)?;
299                    Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(sat)))
300                }
301            }
302
303            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
304            where
305                E: Error,
306            {
307                Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(v)))
308            }
309
310            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
311            where
312                E: Error,
313            {
314                if v < 0 {
315                    return Err(E::custom("amount cannot be negative"));
316                }
317                Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(
318                    v as u64,
319                )))
320            }
321        }
322
323        deserializer.deserialize_any(Visitor)
324    }
325}
326
327impl Serialize for BitcoinAmountOrAll {
328    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
329    where
330        S: Serializer,
331    {
332        match self {
333            Self::All => serializer.serialize_str("all"),
334            Self::Amount(a) => serializer.serialize_u64(a.to_sat()),
335        }
336    }
337}
338
339/// `InPoint` represents a globally unique input in a transaction
340///
341/// Hence, a transaction ID and the input index is required.
342#[derive(
343    Debug,
344    Clone,
345    Copy,
346    Eq,
347    PartialEq,
348    PartialOrd,
349    Ord,
350    Hash,
351    Deserialize,
352    Serialize,
353    Encodable,
354    Decodable,
355)]
356pub struct InPoint {
357    /// The referenced transaction ID
358    pub txid: TransactionId,
359    /// As a transaction may have multiple inputs, this refers to the index of
360    /// the input in a transaction
361    pub in_idx: u64,
362}
363
364impl std::fmt::Display for InPoint {
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366        write!(f, "{}:{}", self.txid, self.in_idx)
367    }
368}
369
370/// `OutPoint` represents a globally unique output in a transaction
371///
372/// Hence, a transaction ID and the output index is required.
373#[derive(
374    Debug,
375    Clone,
376    Copy,
377    Eq,
378    PartialEq,
379    PartialOrd,
380    Ord,
381    Hash,
382    Deserialize,
383    Serialize,
384    Encodable,
385    Decodable,
386)]
387pub struct OutPoint {
388    /// The referenced transaction ID
389    pub txid: TransactionId,
390    /// As a transaction may have multiple outputs, this refers to the index of
391    /// the output in a transaction
392    pub out_idx: u64,
393}
394
395impl std::fmt::Display for OutPoint {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        write!(f, "{}:{}", self.txid, self.out_idx)
398    }
399}
400
401/// A contiguous range of input/output indexes
402#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
403pub struct IdxRange {
404    start: u64,
405    end: u64,
406}
407
408impl IdxRange {
409    pub fn new_single(start: u64) -> Option<Self> {
410        start.checked_add(1).map(|end| Self { start, end })
411    }
412
413    pub fn start(self) -> u64 {
414        self.start
415    }
416
417    pub fn count(self) -> usize {
418        self.into_iter().count()
419    }
420
421    /// Number of indexes in the range, or `None` if the range is descending or
422    /// does not fit into a `usize`.
423    ///
424    /// Ranges are deserialized verbatim from untrusted API requests, so a
425    /// caller validating one has to go through this rather than
426    /// [`Self::count`], which reports a descending range as empty and panics on
427    /// `usize` overflow.
428    pub fn checked_count(self) -> Option<usize> {
429        usize::try_from(self.end.checked_sub(self.start)?).ok()
430    }
431
432    pub fn from_inclusive(range: ops::RangeInclusive<u64>) -> Option<Self> {
433        range.end().checked_add(1).map(|end| Self {
434            start: *range.start(),
435            end,
436        })
437    }
438}
439
440impl From<Range<u64>> for IdxRange {
441    fn from(Range { start, end }: Range<u64>) -> Self {
442        Self { start, end }
443    }
444}
445
446impl IntoIterator for IdxRange {
447    type Item = u64;
448    type IntoIter = ops::Range<u64>;
449
450    fn into_iter(self) -> Self::IntoIter {
451        ops::Range {
452            start: self.start,
453            end: self.end,
454        }
455    }
456}
457
458/// Represents a range of output indices for a single transaction
459#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
460pub struct OutPointRange {
461    pub txid: TransactionId,
462    idx_range: IdxRange,
463}
464
465impl OutPointRange {
466    pub fn new(txid: TransactionId, idx_range: IdxRange) -> Self {
467        Self { txid, idx_range }
468    }
469
470    pub fn new_single(txid: TransactionId, idx: u64) -> Option<Self> {
471        IdxRange::new_single(idx).map(|idx_range| Self { txid, idx_range })
472    }
473
474    pub fn start_idx(self) -> u64 {
475        self.idx_range.start()
476    }
477
478    pub fn out_idx_iter(self) -> impl Iterator<Item = u64> {
479        self.idx_range.into_iter()
480    }
481
482    pub fn count(self) -> usize {
483        self.idx_range.count()
484    }
485
486    /// See [`IdxRange::checked_count`].
487    pub fn checked_count(self) -> Option<usize> {
488        self.idx_range.checked_count()
489    }
490
491    pub fn start_out_point(self) -> OutPoint {
492        OutPoint {
493            txid: self.txid,
494            out_idx: self.idx_range.start(),
495        }
496    }
497
498    pub fn end_out_point(self) -> OutPoint {
499        OutPoint {
500            txid: self.txid,
501            out_idx: self.idx_range.end,
502        }
503    }
504
505    pub fn txid(&self) -> TransactionId {
506        self.txid
507    }
508}
509
510impl IntoIterator for OutPointRange {
511    type Item = OutPoint;
512    type IntoIter = OutPointRangeIter;
513
514    fn into_iter(self) -> Self::IntoIter {
515        OutPointRangeIter {
516            txid: self.txid,
517            inner: self.idx_range.into_iter(),
518        }
519    }
520}
521
522pub struct OutPointRangeIter {
523    txid: TransactionId,
524    inner: ops::Range<u64>,
525}
526
527impl Iterator for OutPointRangeIter {
528    type Item = OutPoint;
529
530    fn next(&mut self) -> Option<Self::Item> {
531        self.inner.next().map(|idx| OutPoint {
532            txid: self.txid,
533            out_idx: idx,
534        })
535    }
536}
537
538impl Encodable for TransactionId {
539    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
540        let bytes = &self[..];
541        writer.write_all(bytes)?;
542        Ok(())
543    }
544}
545
546impl Decodable for TransactionId {
547    fn consensus_decode_partial<D: std::io::Read>(
548        d: &mut D,
549        _modules: &ModuleDecoderRegistry,
550    ) -> Result<Self, DecodeError> {
551        let mut bytes = [0u8; 32];
552        d.read_exact(&mut bytes)?;
553        Ok(Self::from_byte_array(bytes))
554    }
555}
556
557#[derive(
558    Copy,
559    Clone,
560    Debug,
561    PartialEq,
562    Ord,
563    PartialOrd,
564    Eq,
565    Hash,
566    Serialize,
567    Deserialize,
568    Encodable,
569    Decodable,
570)]
571pub struct Feerate {
572    pub sats_per_kvb: u64,
573}
574
575impl fmt::Display for Feerate {
576    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577        f.write_fmt(format_args!("{}sat/kvb", self.sats_per_kvb))
578    }
579}
580
581impl Feerate {
582    pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
583        let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
584        bitcoin::Amount::from_sat(sats)
585    }
586
587    /// Fee for a transaction of the given weight, reproducing the wrapping the
588    /// unchecked multiplication used to do in release profiles.
589    ///
590    /// Consensus behaviour must not depend on the build profile, and
591    /// [`Self::calculate_fee`]'s `*` panics with overflow checks on and wraps
592    /// without them. Sessions ordered before the fee computation was checked
593    /// have to replay as release binaries ran them, so the wrap is explicit.
594    pub fn wrapping_calculate_fee(&self, weight: u64) -> bitcoin::Amount {
595        let sats = weight_to_vbytes(weight).wrapping_mul(self.sats_per_kvb) / 1000;
596        bitcoin::Amount::from_sat(sats)
597    }
598
599    /// Fee for a transaction of the given weight, or `None` if the rate and
600    /// weight do not describe a fee that can exist on chain.
601    ///
602    /// [`Self::calculate_fee`] multiplies unchecked, which wraps to an
603    /// arbitrarily small fee in release profiles. Callers that decide whether
604    /// to accept a transaction must use this instead.
605    pub fn checked_calculate_fee(&self, weight: u64) -> Option<bitcoin::Amount> {
606        let sats = weight_to_vbytes(weight).checked_mul(self.sats_per_kvb)? / 1000;
607        (sats <= bitcoin::Amount::MAX_MONEY.to_sat()).then(|| bitcoin::Amount::from_sat(sats))
608    }
609}
610
611const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
612
613/// Converts weight to virtual bytes, defined in [BIP-141] as weight / 4
614/// (rounded up to the next integer).
615///
616/// [BIP-141]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations
617pub fn weight_to_vbytes(weight: u64) -> u64 {
618    weight.div_ceil(WITNESS_SCALE_FACTOR)
619}
620
621#[derive(Debug, Error)]
622pub enum CoreError {
623    #[error("Mismatching outcome variant: expected {0}, got {1}")]
624    MismatchingVariant(&'static str, &'static str),
625}
626
627// Encode features for a bolt11 invoice without encoding the length.
628// This functionality was available in `lightning` v0.0.123, but has since been
629// removed. See the original code here:
630// https://docs.rs/lightning/0.0.123/src/lightning/ln/features.rs.html#745-750
631// https://docs.rs/lightning/0.0.123/src/lightning/ln/features.rs.html#1008-1012
632pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
633    let mut feature_bytes = vec![];
634    for f in features.le_flags().iter().rev() {
635        f.write(&mut feature_bytes)
636            .expect("Writing to byte vec can't fail");
637    }
638    feature_bytes
639}
640
641/// Outputs hex into an object implementing `fmt::Write`.
642///
643/// Vendored from `bitcoin_hashes` v0.11.0:
644/// <https://docs.rs/bitcoin_hashes/0.11.0/src/bitcoin_hashes/hex.rs.html#173-189>
645pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
646    let prec = f.precision().unwrap_or(2 * data.len());
647    let width = f.width().unwrap_or(2 * data.len());
648    for _ in (2 * data.len())..width {
649        f.write_str("0")?;
650    }
651    for ch in data.iter().take(prec / 2) {
652        write!(f, "{:02x}", *ch)?;
653    }
654    if prec < 2 * data.len() && prec % 2 == 1 {
655        write!(f, "{:x}", data[prec / 2] / 16)?;
656    }
657    Ok(())
658}
659
660/// Gets the (approximate) network from a bitcoin address.
661///
662/// This function mimics how `Address.network` is calculated in bitcoin v0.30.
663/// However, that field was removed in more recent versions in part because it
664/// can only distinguish between `Bitcoin`, `Testnet` and `Regtest`.
665///
666/// As of bitcoin v0.32.4, `Address::is_valid_for_network()` performs equality
667/// checks using `NetworkKind` and `KnownHrp`, which only distinguish between
668/// `Bitcoin`, `Testnet` and `Regtest`.
669/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/address/mod.rs.html#709-716>
670/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/network.rs.html#51-58>
671/// <https://docs.rs/bitcoin/0.32.4/src/bitcoin/address/mod.rs.html#200-209>
672pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
673    if address.is_valid_for_network(Network::Bitcoin) {
674        Network::Bitcoin
675    } else if address.is_valid_for_network(Network::Testnet) {
676        Network::Testnet
677    } else if address.is_valid_for_network(Network::Regtest) {
678        Network::Regtest
679    } else {
680        panic!("Address is not valid for any network");
681    }
682}
683
684/// Returns the default esplora server according to the network
685pub fn default_esplora_server(network: Network, port: Option<String>) -> BitcoinRpcConfig {
686    BitcoinRpcConfig {
687        kind: "esplora".to_string(),
688        url: match network {
689            Network::Bitcoin => SafeUrl::parse("https://mempool.space/api/"),
690            Network::Testnet => SafeUrl::parse("https://mempool.space/testnet/api/"),
691            Network::Testnet4 => SafeUrl::parse("https://mempool.space/testnet4/api/"),
692            Network::Signet => SafeUrl::parse("https://mutinynet.com/api/"),
693            Network::Regtest => SafeUrl::parse(&format!(
694                "http://127.0.0.1:{}/",
695                port.unwrap_or_else(|| String::from("50002"))
696            )),
697        }
698        .expect("Failed to parse default esplora server"),
699    }
700}
701
702#[cfg(test)]
703mod tests;