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