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