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
21extern crate self as fedimint_core;
40
41#[cfg(feature = "uniffi")]
42uniffi::setup_scaffolding!();
43
44use std::fmt::{self, Debug};
45use std::io::Error;
46use std::ops::{self, Range};
47use std::str::FromStr;
48
49pub use amount::*;
50use bitcoin::address::NetworkUnchecked;
51pub use bitcoin::hashes::Hash as BitcoinHash;
52use bitcoin::{Address, Network};
53use envs::BitcoinRpcConfig;
54use lightning::util::ser::Writeable;
55use lightning_types::features::Bolt11InvoiceFeatures;
56pub use macro_rules_attribute::apply;
57pub use peer_id::*;
58use serde::{Deserialize, Deserializer, Serialize, Serializer};
59use thiserror::Error;
60pub use tiered::Tiered;
61pub use tiered_multi::*;
62use util::SafeUrl;
63pub use {bitcoin, hex, secp256k1};
64
65use crate::encoding::{Decodable, DecodeError, Encodable};
66use crate::module::registry::ModuleDecoderRegistry;
67
68pub mod admin_client;
70mod amount;
72pub mod backup;
74pub mod bls12_381_serde;
76pub mod config;
78pub mod core;
80pub mod db;
82pub mod encoding;
84pub mod endpoint_constants;
85pub mod envs;
87pub mod fmt_utils;
89pub mod invite_code;
91pub mod log;
92#[macro_use]
94pub mod macros;
95pub mod base32;
97pub mod module;
99pub mod net;
101mod peer_id;
103pub mod runtime;
105pub mod rustls;
107pub mod setup_code;
109pub mod task;
111pub mod tiered;
113pub mod tiered_multi;
115pub mod time;
117pub mod timing;
119pub mod transaction;
121pub mod txoproof;
123pub mod util;
125pub mod version;
127
128pub mod session_outcome;
130
131mod txid {
135 use bitcoin::hashes::hash_newtype;
136 use bitcoin::hashes::sha256::Hash as Sha256;
137
138 hash_newtype!(
139 pub struct TransactionId(Sha256);
141 );
142}
143pub use txid::TransactionId;
144
145#[cfg(feature = "uniffi")]
146uniffi::custom_type!(TransactionId, String, {
147 lower: |txid| txid.to_string(),
148 try_lift: |s| TransactionId::from_str(&s).map_err(Into::into),
149});
150
151pub struct TransactionIdShortFmt<'a>(&'a TransactionId);
152pub struct TransactionIdFullFmt<'a>(&'a TransactionId);
153
154impl TransactionId {
155 pub fn fmt_short(&self) -> TransactionIdShortFmt<'_> {
156 TransactionIdShortFmt(self)
157 }
158
159 pub fn fmt_full(&self) -> TransactionIdFullFmt<'_> {
160 TransactionIdFullFmt(self)
161 }
162}
163
164impl std::fmt::Display for TransactionIdShortFmt<'_> {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 let bytes = &self.0[..];
167 format_hex(&bytes[..4], f)?;
168 f.write_str("_")?;
169 format_hex(&bytes[28..], f)
170 }
171}
172
173impl std::fmt::Display for TransactionIdFullFmt<'_> {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 let bytes = &self.0[..];
176 format_hex(bytes, f)
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)]
191pub struct ChainId(pub bitcoin::BlockHash);
192
193#[cfg(feature = "uniffi")]
194uniffi::custom_type!(ChainId, String, {
195 lower: |c| c.to_string(),
196 try_lift: |s| ChainId::from_str(&s).map_err(Into::into),
197});
198
199impl ChainId {
200 pub fn new(block_hash: bitcoin::BlockHash) -> Self {
202 Self(block_hash)
203 }
204
205 pub fn block_hash(&self) -> bitcoin::BlockHash {
207 self.0
208 }
209}
210
211impl From<bitcoin::BlockHash> for ChainId {
212 fn from(block_hash: bitcoin::BlockHash) -> Self {
213 Self(block_hash)
214 }
215}
216
217impl From<ChainId> for bitcoin::BlockHash {
218 fn from(chain_id: ChainId) -> Self {
219 chain_id.0
220 }
221}
222
223impl std::fmt::Display for ChainId {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 write!(f, "{}", self.0)
226 }
227}
228
229impl FromStr for ChainId {
230 type Err = bitcoin::hashes::hex::HexToArrayError;
231
232 fn from_str(s: &str) -> Result<Self, Self::Err> {
233 bitcoin::BlockHash::from_str(s).map(Self)
234 }
235}
236
237impl Serialize for ChainId {
238 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
239 where
240 S: Serializer,
241 {
242 self.0.serialize(serializer)
243 }
244}
245
246impl<'de> Deserialize<'de> for ChainId {
247 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
248 where
249 D: Deserializer<'de>,
250 {
251 bitcoin::BlockHash::deserialize(deserializer).map(Self)
252 }
253}
254
255#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone)]
257pub enum BitcoinAmountOrAll {
258 All,
259 Amount(bitcoin::Amount),
260}
261
262impl std::fmt::Display for BitcoinAmountOrAll {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 match self {
265 Self::All => write!(f, "all"),
266 Self::Amount(amount) => write!(f, "{amount}"),
267 }
268 }
269}
270
271impl FromStr for BitcoinAmountOrAll {
272 type Err = ParseBitcoinAmountOrAllError;
273
274 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
275 if s.eq_ignore_ascii_case("all") {
276 Ok(Self::All)
277 } else {
278 let amount = Amount::from_str(s)?;
279 Ok(Self::Amount(amount.try_into()?))
280 }
281 }
282}
283
284#[derive(Debug, Error)]
286#[non_exhaustive]
287pub enum ParseBitcoinAmountOrAllError {
288 #[error("Invalid amount: {0}")]
290 Amount(#[from] ParseAmountError),
291 #[error("Amount cannot be expressed in satoshis: {0}")]
293 Precision(#[from] AmountConversionError),
294}
295
296impl<'de> Deserialize<'de> for BitcoinAmountOrAll {
298 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
299 where
300 D: Deserializer<'de>,
301 {
302 use serde::de::Error;
303
304 struct Visitor;
305
306 impl serde::de::Visitor<'_> for Visitor {
307 type Value = BitcoinAmountOrAll;
308
309 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
310 write!(f, "a bitcoin amount as number or 'all'")
311 }
312
313 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
314 where
315 E: Error,
316 {
317 if v.eq_ignore_ascii_case("all") {
318 Ok(BitcoinAmountOrAll::All)
319 } else {
320 let sat: u64 = v.parse().map_err(E::custom)?;
321 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(sat)))
322 }
323 }
324
325 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
326 where
327 E: Error,
328 {
329 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(v)))
330 }
331
332 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
333 where
334 E: Error,
335 {
336 if v < 0 {
337 return Err(E::custom("amount cannot be negative"));
338 }
339 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(
340 v as u64,
341 )))
342 }
343 }
344
345 deserializer.deserialize_any(Visitor)
346 }
347}
348
349impl Serialize for BitcoinAmountOrAll {
350 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
351 where
352 S: Serializer,
353 {
354 match self {
355 Self::All => serializer.serialize_str("all"),
356 Self::Amount(a) => serializer.serialize_u64(a.to_sat()),
357 }
358 }
359}
360
361#[derive(
365 Debug,
366 Clone,
367 Copy,
368 Eq,
369 PartialEq,
370 PartialOrd,
371 Ord,
372 Hash,
373 Deserialize,
374 Serialize,
375 Encodable,
376 Decodable,
377)]
378pub struct InPoint {
379 pub txid: TransactionId,
381 pub in_idx: u64,
384}
385
386impl std::fmt::Display for InPoint {
387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388 write!(f, "{}:{}", self.txid, self.in_idx)
389 }
390}
391
392#[derive(
396 Debug,
397 Clone,
398 Copy,
399 Eq,
400 PartialEq,
401 PartialOrd,
402 Ord,
403 Hash,
404 Deserialize,
405 Serialize,
406 Encodable,
407 Decodable,
408)]
409#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
410pub struct OutPoint {
411 pub txid: TransactionId,
413 pub out_idx: u64,
416}
417
418impl std::fmt::Display for OutPoint {
419 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420 write!(f, "{}:{}", self.txid, self.out_idx)
421 }
422}
423
424#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
426pub struct IdxRange {
427 start: u64,
428 end: u64,
429}
430
431impl IdxRange {
432 pub fn new_single(start: u64) -> Option<Self> {
433 start.checked_add(1).map(|end| Self { start, end })
434 }
435
436 pub fn start(self) -> u64 {
437 self.start
438 }
439
440 pub fn count(self) -> usize {
441 self.into_iter().count()
442 }
443
444 pub fn checked_count(self) -> Option<usize> {
452 usize::try_from(self.end.checked_sub(self.start)?).ok()
453 }
454
455 pub fn from_inclusive(range: ops::RangeInclusive<u64>) -> Option<Self> {
456 range.end().checked_add(1).map(|end| Self {
457 start: *range.start(),
458 end,
459 })
460 }
461}
462
463impl From<Range<u64>> for IdxRange {
464 fn from(Range { start, end }: Range<u64>) -> Self {
465 Self { start, end }
466 }
467}
468
469impl IntoIterator for IdxRange {
470 type Item = u64;
471 type IntoIter = ops::Range<u64>;
472
473 fn into_iter(self) -> Self::IntoIter {
474 ops::Range {
475 start: self.start,
476 end: self.end,
477 }
478 }
479}
480
481#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
483pub struct OutPointRange {
484 pub txid: TransactionId,
485 idx_range: IdxRange,
486}
487
488impl OutPointRange {
489 pub fn new(txid: TransactionId, idx_range: IdxRange) -> Self {
490 Self { txid, idx_range }
491 }
492
493 pub fn new_single(txid: TransactionId, idx: u64) -> Option<Self> {
494 IdxRange::new_single(idx).map(|idx_range| Self { txid, idx_range })
495 }
496
497 pub fn start_idx(self) -> u64 {
498 self.idx_range.start()
499 }
500
501 pub fn out_idx_iter(self) -> impl Iterator<Item = u64> {
502 self.idx_range.into_iter()
503 }
504
505 pub fn count(self) -> usize {
506 self.idx_range.count()
507 }
508
509 pub fn checked_count(self) -> Option<usize> {
511 self.idx_range.checked_count()
512 }
513
514 pub fn start_out_point(self) -> OutPoint {
515 OutPoint {
516 txid: self.txid,
517 out_idx: self.idx_range.start(),
518 }
519 }
520
521 pub fn end_out_point(self) -> OutPoint {
522 OutPoint {
523 txid: self.txid,
524 out_idx: self.idx_range.end,
525 }
526 }
527
528 pub fn txid(&self) -> TransactionId {
529 self.txid
530 }
531}
532
533impl IntoIterator for OutPointRange {
534 type Item = OutPoint;
535 type IntoIter = OutPointRangeIter;
536
537 fn into_iter(self) -> Self::IntoIter {
538 OutPointRangeIter {
539 txid: self.txid,
540 inner: self.idx_range.into_iter(),
541 }
542 }
543}
544
545pub struct OutPointRangeIter {
546 txid: TransactionId,
547 inner: ops::Range<u64>,
548}
549
550impl Iterator for OutPointRangeIter {
551 type Item = OutPoint;
552
553 fn next(&mut self) -> Option<Self::Item> {
554 self.inner.next().map(|idx| OutPoint {
555 txid: self.txid,
556 out_idx: idx,
557 })
558 }
559}
560
561impl Encodable for TransactionId {
562 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
563 let bytes = &self[..];
564 writer.write_all(bytes)?;
565 Ok(())
566 }
567}
568
569impl Decodable for TransactionId {
570 fn consensus_decode_partial<D: std::io::Read>(
571 d: &mut D,
572 _modules: &ModuleDecoderRegistry,
573 ) -> Result<Self, DecodeError> {
574 let mut bytes = [0u8; 32];
575 d.read_exact(&mut bytes)?;
576 Ok(Self::from_byte_array(bytes))
577 }
578}
579
580#[derive(
581 Copy,
582 Clone,
583 Debug,
584 PartialEq,
585 Ord,
586 PartialOrd,
587 Eq,
588 Hash,
589 Serialize,
590 Deserialize,
591 Encodable,
592 Decodable,
593)]
594pub struct Feerate {
595 pub sats_per_kvb: u64,
596}
597
598impl fmt::Display for Feerate {
599 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
600 f.write_fmt(format_args!("{}sat/kvb", self.sats_per_kvb))
601 }
602}
603
604impl Feerate {
605 pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
606 let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
607 bitcoin::Amount::from_sat(sats)
608 }
609
610 pub fn wrapping_calculate_fee(&self, weight: u64) -> bitcoin::Amount {
618 let sats = weight_to_vbytes(weight).wrapping_mul(self.sats_per_kvb) / 1000;
619 bitcoin::Amount::from_sat(sats)
620 }
621
622 pub fn checked_calculate_fee(&self, weight: u64) -> Option<bitcoin::Amount> {
629 let sats = weight_to_vbytes(weight).checked_mul(self.sats_per_kvb)? / 1000;
630 (sats <= bitcoin::Amount::MAX_MONEY.to_sat()).then(|| bitcoin::Amount::from_sat(sats))
631 }
632}
633
634const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
635
636pub fn weight_to_vbytes(weight: u64) -> u64 {
641 weight.div_ceil(WITNESS_SCALE_FACTOR)
642}
643
644#[derive(Debug, Error)]
645pub enum CoreError {
646 #[error("Mismatching outcome variant: expected {0}, got {1}")]
647 MismatchingVariant(&'static str, &'static str),
648}
649
650pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
656 let mut feature_bytes = vec![];
657 for f in features.le_flags().iter().rev() {
658 f.write(&mut feature_bytes)
659 .expect("Writing to byte vec can't fail");
660 }
661 feature_bytes
662}
663
664pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
669 let prec = f.precision().unwrap_or(2 * data.len());
670 let width = f.width().unwrap_or(2 * data.len());
671 for _ in (2 * data.len())..width {
672 f.write_str("0")?;
673 }
674 for ch in data.iter().take(prec / 2) {
675 write!(f, "{:02x}", *ch)?;
676 }
677 if prec < 2 * data.len() && prec % 2 == 1 {
678 write!(f, "{:x}", data[prec / 2] / 16)?;
679 }
680 Ok(())
681}
682
683pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
696 if address.is_valid_for_network(Network::Bitcoin) {
697 Network::Bitcoin
698 } else if address.is_valid_for_network(Network::Testnet) {
699 Network::Testnet
700 } else if address.is_valid_for_network(Network::Regtest) {
701 Network::Regtest
702 } else {
703 panic!("Address is not valid for any network");
704 }
705}
706
707pub fn default_esplora_server(network: Network, port: Option<String>) -> BitcoinRpcConfig {
709 BitcoinRpcConfig {
710 kind: "esplora".to_string(),
711 url: match network {
712 Network::Bitcoin => SafeUrl::parse("https://mempool.space/api/"),
713 Network::Testnet => SafeUrl::parse("https://mempool.space/testnet/api/"),
714 Network::Testnet4 => SafeUrl::parse("https://mempool.space/testnet4/api/"),
715 Network::Signet => SafeUrl::parse("https://mutinynet.com/api/"),
716 Network::Regtest => SafeUrl::parse(&format!(
717 "http://127.0.0.1:{}/",
718 port.unwrap_or_else(|| String::from("50002"))
719 )),
720 }
721 .expect("Failed to parse default esplora server"),
722 }
723}
724
725#[cfg(test)]
726mod tests;