/ fedimint-core / src / lib.rs
lib.rs
  1  //! Fedimint Core library
  2  //!
  3  //! `fedimint-core` contains are commonly used types, utilities and primitives,
  4  //! shared between both client and server code.
  5  //!
  6  //! Things that are server-side only typically live in `fedimint-server`, and
  7  //! client-side only in `fedimint-client`.
  8  //!
  9  //! ### Wasm support
 10  //!
 11  //! All code in `fedimint-core` needs to compile on Wasm, and `fedimint-core`
 12  //! includes helpers and wrappers around non-wasm-safe utitlies.
 13  //!
 14  //! In particular:
 15  //!
 16  //! * [`fedimint_core::task`] for task spawning and control
 17  //! * [`fedimint_core::time`] for time-related operations
 18  
 19  #![allow(where_clauses_object_safety)] // https://github.com/dtolnay/async-trait/issues/228
 20  extern crate self as fedimint_core;
 21  
 22  use std::collections::{BTreeMap, BTreeSet};
 23  use std::fmt::Debug;
 24  use std::io::Error;
 25  use std::num::ParseIntError;
 26  use std::str::FromStr;
 27  
 28  /// Mostly re-exported for [`Decodable`] macros.
 29  pub use anyhow;
 30  use anyhow::bail;
 31  use bitcoin::Denomination;
 32  use bitcoin_hashes::hash_newtype;
 33  use bitcoin_hashes::sha256::Hash as Sha256;
 34  pub use bitcoin_hashes::Hash as BitcoinHash;
 35  use fedimint_core::config::PeerUrl;
 36  pub use macro_rules_attribute::apply;
 37  pub use module::ServerModule;
 38  pub use secp256k1;
 39  use serde::{Deserialize, Serialize};
 40  use thiserror::Error;
 41  pub use tiered::Tiered;
 42  pub use tiered_multi::*;
 43  
 44  pub use crate::core::server;
 45  use crate::encoding::{Decodable, DecodeError, Encodable};
 46  use crate::module::registry::ModuleDecoderRegistry;
 47  
 48  /// Admin (guardian) client types
 49  pub mod admin_client;
 50  /// Federation-stored client backups
 51  pub mod backup;
 52  /// Gradual bitcoin dependency migration helpers
 53  pub mod bitcoin_migration;
 54  /// Legacy serde encoding for bls12_381
 55  pub mod bls12_381_serde;
 56  /// Federation configuration
 57  pub mod config;
 58  /// Fundamental types
 59  pub mod core;
 60  /// Database handling
 61  pub mod db;
 62  /// Consensus encoding
 63  pub mod encoding;
 64  pub mod endpoint_constants;
 65  /// Common environment variables
 66  pub mod envs;
 67  pub mod epoch;
 68  /// Formatting helpers
 69  pub mod fmt_utils;
 70  /// Hex encoding helpers
 71  pub mod hex;
 72  /// Federation invite code
 73  pub mod invite_code;
 74  /// Common macros
 75  #[macro_use]
 76  pub mod macros;
 77  /// Extenable module sysystem
 78  pub mod module;
 79  /// Peer networking
 80  pub mod net;
 81  /// Runtime (wasm32 vs native) differences handling
 82  pub mod runtime;
 83  /// Task handling, including wasm safe logic
 84  pub mod task;
 85  /// Types handling per-denomination values
 86  pub mod tiered;
 87  /// Types handling multiple per-denomination values
 88  pub mod tiered_multi;
 89  /// Time handling, wasm safe functionality
 90  pub mod time;
 91  /// Timing helpers
 92  pub mod timing;
 93  /// Fedimint transaction (inpus + outputs + signature) types
 94  pub mod transaction;
 95  /// Peg-in txo proofs
 96  pub mod txoproof;
 97  /// General purpose utilities
 98  pub mod util;
 99  
100  /// Atomic BFT unit containing consensus items
101  pub mod session_outcome;
102  
103  hash_newtype!(
104      /// A transaction id for peg-ins, peg-outs and reissuances
105      pub struct TransactionId(Sha256);
106  );
107  
108  #[derive(
109      Debug,
110      Clone,
111      Copy,
112      PartialEq,
113      Eq,
114      Hash,
115      PartialOrd,
116      Ord,
117      Serialize,
118      Deserialize,
119      Encodable,
120      Decodable,
121  )]
122  pub struct PeerId(u16);
123  
124  impl FromStr for PeerId {
125      type Err = <u16 as FromStr>::Err;
126  
127      fn from_str(s: &str) -> Result<Self, Self::Err> {
128          s.parse().map(PeerId)
129      }
130  }
131  
132  pub const SATS_PER_BITCOIN: u64 = 100_000_000;
133  
134  /// Represents an amount of BTC inside the system. The base denomination is
135  /// milli satoshi for now, this is also why the amount type from rust-bitcoin
136  /// isn't used instead.
137  #[derive(
138      Debug,
139      Clone,
140      Copy,
141      Eq,
142      PartialEq,
143      Ord,
144      PartialOrd,
145      Hash,
146      Deserialize,
147      Serialize,
148      Encodable,
149      Decodable,
150  )]
151  #[serde(transparent)]
152  pub struct Amount {
153      pub msats: u64,
154  }
155  
156  impl Amount {
157      pub const ZERO: Self = Self { msats: 0 };
158  
159      pub const fn from_msats(msats: u64) -> Amount {
160          Amount { msats }
161      }
162  
163      pub const fn from_sats(sats: u64) -> Amount {
164          Amount::from_msats(sats * 1000)
165      }
166  
167      pub const fn from_bitcoins(bitcoins: u64) -> Amount {
168          Amount::from_sats(bitcoins * SATS_PER_BITCOIN)
169      }
170  
171      pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
172          if let Denomination::MilliSatoshi = denom {
173              return Ok(Self::from_msats(s.parse()?));
174          }
175          let btc_amt = bitcoin::amount::Amount::from_str_in(s, denom)?;
176          Ok(Self::from(btc_amt))
177      }
178  
179      pub fn saturating_sub(self, other: Amount) -> Self {
180          Amount {
181              msats: self.msats.saturating_sub(other.msats),
182          }
183      }
184  
185      pub fn mul_u64(self, other: u64) -> Self {
186          Amount {
187              msats: self.msats * other,
188          }
189      }
190  
191      // Makes sure we're dealing with a precision of satoshi or higher
192      pub fn ensure_sats_precision(&self) -> anyhow::Result<()> {
193          if self.msats % 1000 != 0 {
194              bail!("Amount is using a precision smaller than satoshi, cannot convert to satoshis");
195          }
196          Ok(())
197      }
198  
199      pub fn try_into_sats(&self) -> anyhow::Result<u64> {
200          self.ensure_sats_precision()?;
201          Ok(self.msats / 1000)
202      }
203  
204      pub const fn sats_round_down(&self) -> u64 {
205          self.msats / 1000
206      }
207  
208      pub fn sats_f64(&self) -> f64 {
209          self.msats as f64 / 1000.0
210      }
211  
212      pub fn checked_sub(self, other: Amount) -> Option<Self> {
213          Some(Self {
214              msats: self.msats.checked_sub(other.msats)?,
215          })
216      }
217  }
218  
219  /// Shorthand for [`Amount::from_msats`]
220  ///
221  /// Useful only for tests, but it's so common that it makes sense to have
222  /// it in the main `fedimint-api` crate.
223  pub fn msats(msats: u64) -> Amount {
224      Amount::from_msats(msats)
225  }
226  
227  /// Shorthand for [`Amount::from_sats`]
228  pub fn sats(amount: u64) -> Amount {
229      Amount::from_sats(amount)
230  }
231  
232  pub mod amount {
233      pub mod serde {
234          pub mod as_msat {
235              //! Serialize and deserialize [`Amount`](crate::Amount) as integers
236              //! denominated in milli-satoshi. Use with
237              //! `#[serde(with = "amount::serde::as_msat")]`.
238  
239              use serde::{Deserialize, Deserializer, Serialize, Serializer};
240  
241              pub fn serialize<S: Serializer>(a: &crate::Amount, s: S) -> Result<S::Ok, S::Error> {
242                  u64::serialize(&a.msats, s)
243              }
244  
245              pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<crate::Amount, D::Error> {
246                  Ok(crate::Amount::from_msats(u64::deserialize(d)?))
247              }
248          }
249      }
250  }
251  
252  /// Amount of bitcoin to send, or "all" to send all available funds
253  #[derive(Debug, Eq, PartialEq, Copy, Hash, Clone, Serialize, Deserialize)]
254  #[serde(rename_all = "snake_case")]
255  pub enum BitcoinAmountOrAll {
256      All,
257      #[serde(untagged)]
258      Amount(#[serde(with = "bitcoin::amount::serde::as_sat")] bitcoin::Amount),
259  }
260  
261  impl FromStr for BitcoinAmountOrAll {
262      type Err = anyhow::Error;
263  
264      fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
265          if s == "all" {
266              Ok(BitcoinAmountOrAll::All)
267          } else {
268              let amount = crate::Amount::from_str(s)?;
269              Ok(BitcoinAmountOrAll::Amount(amount.try_into()?))
270          }
271      }
272  }
273  
274  /// `OutPoint` represents a globally unique output in a transaction
275  ///
276  /// Hence, a transaction ID and the output index is required.
277  #[derive(
278      Debug,
279      Clone,
280      Copy,
281      Eq,
282      PartialEq,
283      PartialOrd,
284      Ord,
285      Hash,
286      Deserialize,
287      Serialize,
288      Encodable,
289      Decodable,
290  )]
291  pub struct OutPoint {
292      /// The referenced transaction ID
293      pub txid: TransactionId,
294      /// As a transaction may have multiple outputs, this refers to the index of
295      /// the output in a transaction
296      pub out_idx: u64,
297  }
298  
299  #[derive(Error, Debug)]
300  pub enum ParseAmountError {
301      #[error("Error parsing string as integer: {0}")]
302      NotANumber(#[from] ParseIntError),
303      #[error("Error parsing string as a bitcoin amount: {0}")]
304      WrongBitcoinAmount(#[from] bitcoin::amount::ParseAmountError),
305  }
306  
307  impl<T> NumPeersExt for BTreeMap<PeerId, T> {
308      fn total(&self) -> usize {
309          self.len()
310      }
311  }
312  
313  #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
314  pub struct NumPeers(usize);
315  
316  impl From<usize> for NumPeers {
317      fn from(value: usize) -> Self {
318          Self(value)
319      }
320  }
321  impl NumPeers {
322      pub fn as_usize(&self) -> usize {
323          self.0
324      }
325  }
326  
327  impl NumPeersExt for NumPeers {
328      fn total(&self) -> usize {
329          self.0
330      }
331  }
332  
333  impl NumPeersExt for &[PeerId] {
334      fn total(&self) -> usize {
335          self.len()
336      }
337  }
338  
339  impl NumPeersExt for Vec<PeerId> {
340      fn total(&self) -> usize {
341          self.len()
342      }
343  }
344  
345  impl NumPeersExt for Vec<PeerUrl> {
346      fn total(&self) -> usize {
347          self.len()
348      }
349  }
350  
351  impl NumPeersExt for BTreeSet<PeerId> {
352      fn total(&self) -> usize {
353          self.len()
354      }
355  }
356  
357  /// for consensus-related calculations given the number of peers
358  pub trait NumPeersExt {
359      fn total(&self) -> usize;
360  
361      /// number of peers that can be evil without disrupting the federation
362      fn max_evil(&self) -> usize {
363          (self.total() - 1) / 3
364      }
365  
366      /// number of peers to select such that one is honest (under our
367      /// assumptions)
368      fn one_honest(&self) -> usize {
369          self.max_evil() + 1
370      }
371  
372      /// Degree of a underlying polynomial to require `threshold` signatures
373      fn degree(&self) -> usize {
374          self.threshold() - 1
375      }
376  
377      /// number of peers required for a signature
378      fn threshold(&self) -> usize {
379          self.total() - self.max_evil()
380      }
381  }
382  
383  impl PeerId {
384      pub fn to_usize(self) -> usize {
385          self.0 as usize
386      }
387  }
388  
389  impl std::fmt::Display for PeerId {
390      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391          write!(f, "{}", self.0)
392      }
393  }
394  
395  impl From<u16> for PeerId {
396      fn from(id: u16) -> Self {
397          Self(id)
398      }
399  }
400  
401  impl From<PeerId> for u16 {
402      fn from(peer: PeerId) -> u16 {
403          peer.0
404      }
405  }
406  
407  impl std::fmt::Display for Amount {
408      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409          write!(f, "{} msat", self.msats)
410      }
411  }
412  
413  impl std::fmt::Display for OutPoint {
414      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415          write!(f, "{}:{}", self.txid, self.out_idx)
416      }
417  }
418  
419  impl std::ops::Rem for Amount {
420      type Output = Amount;
421  
422      fn rem(self, rhs: Self) -> Self::Output {
423          Amount {
424              msats: self.msats % rhs.msats,
425          }
426      }
427  }
428  
429  impl std::ops::RemAssign for Amount {
430      fn rem_assign(&mut self, rhs: Self) {
431          self.msats %= rhs.msats;
432      }
433  }
434  
435  impl std::ops::Div for Amount {
436      type Output = u64;
437  
438      fn div(self, rhs: Self) -> Self::Output {
439          self.msats / rhs.msats
440      }
441  }
442  
443  impl std::ops::SubAssign for Amount {
444      fn sub_assign(&mut self, rhs: Self) {
445          self.msats -= rhs.msats
446      }
447  }
448  
449  impl std::ops::Mul<u64> for Amount {
450      type Output = Amount;
451  
452      fn mul(self, rhs: u64) -> Self::Output {
453          Amount {
454              msats: self.msats * rhs,
455          }
456      }
457  }
458  
459  impl std::ops::Mul<Amount> for u64 {
460      type Output = Amount;
461  
462      fn mul(self, rhs: Amount) -> Self::Output {
463          Amount {
464              msats: self * rhs.msats,
465          }
466      }
467  }
468  
469  impl std::ops::Add for Amount {
470      type Output = Amount;
471  
472      fn add(self, rhs: Self) -> Self::Output {
473          Amount {
474              msats: self.msats + rhs.msats,
475          }
476      }
477  }
478  
479  impl std::ops::AddAssign for Amount {
480      fn add_assign(&mut self, rhs: Self) {
481          *self = *self + rhs;
482      }
483  }
484  
485  impl std::iter::Sum for Amount {
486      fn sum<I: Iterator<Item = Amount>>(iter: I) -> Self {
487          Amount {
488              msats: iter.map(|amt| amt.msats).sum::<u64>(),
489          }
490      }
491  }
492  
493  impl std::ops::Sub for Amount {
494      type Output = Amount;
495  
496      fn sub(self, rhs: Self) -> Self::Output {
497          Amount {
498              msats: self.msats - rhs.msats,
499          }
500      }
501  }
502  
503  impl FromStr for Amount {
504      type Err = ParseAmountError;
505  
506      fn from_str(s: &str) -> Result<Self, Self::Err> {
507          if let Some(i) = s.find(char::is_alphabetic) {
508              let (amt, denom) = s.split_at(i);
509              Amount::from_str_in(amt.trim(), denom.trim().parse()?)
510          } else {
511              // default to millisatoshi
512              Amount::from_str_in(s.trim(), bitcoin::Denomination::MilliSatoshi)
513          }
514      }
515  }
516  
517  impl From<bitcoin::Amount> for Amount {
518      fn from(amt: bitcoin::Amount) -> Self {
519          assert!(amt.to_sat() <= 2_100_000_000_000_000);
520          Amount {
521              msats: amt.to_sat() * 1000,
522          }
523      }
524  }
525  
526  impl TryFrom<Amount> for bitcoin::Amount {
527      type Error = anyhow::Error;
528  
529      fn try_from(value: Amount) -> anyhow::Result<Self> {
530          value.try_into_sats().map(bitcoin::Amount::from_sat)
531      }
532  }
533  
534  impl Encodable for TransactionId {
535      fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
536          let bytes = &self[..];
537          writer.write_all(bytes)?;
538          Ok(bytes.len())
539      }
540  }
541  
542  impl Decodable for TransactionId {
543      fn consensus_decode<D: std::io::Read>(
544          d: &mut D,
545          _modules: &ModuleDecoderRegistry,
546      ) -> Result<Self, DecodeError> {
547          let mut bytes = [0u8; 32];
548          d.read_exact(&mut bytes).map_err(DecodeError::from_err)?;
549          Ok(TransactionId::from_byte_array(bytes))
550      }
551  }
552  
553  #[derive(
554      Copy,
555      Clone,
556      Debug,
557      PartialEq,
558      Ord,
559      PartialOrd,
560      Eq,
561      Hash,
562      Serialize,
563      Deserialize,
564      Encodable,
565      Decodable,
566  )]
567  pub struct Feerate {
568      pub sats_per_kvb: u64,
569  }
570  
571  impl Feerate {
572      pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
573          let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
574          bitcoin::Amount::from_sat(sats)
575      }
576  }
577  
578  const WITNESS_SCALE_FACTOR: u64 = bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR as u64;
579  
580  /// Converts weight to virtual bytes, defined in [BIP-141] as weight / 4
581  /// (rounded up to the next integer).
582  ///
583  /// [BIP-141]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations
584  pub fn weight_to_vbytes(weight: u64) -> u64 {
585      (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR
586  }
587  
588  #[derive(Debug, Error)]
589  pub enum CoreError {
590      #[error("Mismatching outcome variant: expected {0}, got {1}")]
591      MismatchingVariant(&'static str, &'static str),
592  }
593  
594  #[cfg(test)]
595  mod tests {
596      use super::*;
597  
598      #[test]
599      fn amount_multiplication_by_scalar() {
600          assert_eq!(Amount::from_msats(1000) * 123, Amount::from_msats(123_000));
601      }
602  
603      #[test]
604      fn scalar_multiplication_by_amount() {
605          assert_eq!(123 * Amount::from_msats(1000), Amount::from_msats(123_000));
606      }
607  
608      #[test]
609      fn converts_weight_to_vbytes() {
610          assert_eq!(1, weight_to_vbytes(4));
611          assert_eq!(2, weight_to_vbytes(5));
612      }
613  
614      #[test]
615      fn calculate_fee() {
616          let feerate = Feerate { sats_per_kvb: 1000 };
617          assert_eq!(bitcoin::Amount::from_sat(25), feerate.calculate_fee(100));
618          assert_eq!(bitcoin::Amount::from_sat(26), feerate.calculate_fee(101));
619      }
620  
621      #[test]
622      fn test_amount_parsing() {
623          // msats
624          assert_eq!(Amount::from_msats(123), Amount::from_str("123").unwrap());
625          assert_eq!(
626              Amount::from_msats(123),
627              Amount::from_str("123msat").unwrap()
628          );
629          assert_eq!(
630              Amount::from_msats(123),
631              Amount::from_str("123 msat").unwrap()
632          );
633          assert_eq!(
634              Amount::from_msats(123),
635              Amount::from_str("123 msats").unwrap()
636          );
637          // sats
638          assert_eq!(Amount::from_sats(123), Amount::from_str("123sat").unwrap());
639          assert_eq!(Amount::from_sats(123), Amount::from_str("123 sat").unwrap());
640          assert_eq!(
641              Amount::from_sats(123),
642              Amount::from_str("123satoshi").unwrap()
643          );
644          assert_eq!(
645              Amount::from_sats(123),
646              Amount::from_str("123satoshis").unwrap()
647          );
648          // btc
649          assert_eq!(
650              Amount::from_bitcoins(123),
651              Amount::from_str("123btc").unwrap()
652          );
653          assert_eq!(
654              Amount::from_sats(12_345_600_000),
655              Amount::from_str("123.456btc").unwrap()
656          );
657      }
658  
659      #[test]
660      fn test_deserialize_amount_or_all() {
661          let all: BitcoinAmountOrAll = serde_json::from_str("\"all\"").unwrap();
662          assert_eq!(all, BitcoinAmountOrAll::All);
663  
664          let all: BitcoinAmountOrAll = serde_json::from_str("12345").unwrap();
665          assert_eq!(
666              all,
667              BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(12345))
668          );
669      }
670  }