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
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
65pub mod admin_client;
67mod amount;
69pub mod backup;
71pub mod bls12_381_serde;
73pub mod config;
75pub mod core;
77pub mod db;
79pub mod encoding;
81pub mod endpoint_constants;
82pub mod envs;
84pub mod fmt_utils;
86pub mod invite_code;
88pub mod log;
89#[macro_use]
91pub mod macros;
92pub mod base32;
94pub mod module;
96pub mod net;
98mod peer_id;
100pub mod runtime;
102pub mod rustls;
104pub mod setup_code;
106pub mod task;
108pub mod tiered;
110pub mod tiered_multi;
112pub mod time;
114pub mod timing;
116pub mod transaction;
118pub mod txoproof;
120pub mod util;
122pub mod version;
124
125pub mod session_outcome;
127
128mod 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)]
174pub struct ChainId(pub bitcoin::BlockHash);
175
176impl ChainId {
177 pub fn new(block_hash: bitcoin::BlockHash) -> Self {
179 Self(block_hash)
180 }
181
182 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#[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#[derive(Debug, Error)]
263#[non_exhaustive]
264pub enum ParseBitcoinAmountOrAllError {
265 #[error("Invalid amount: {0}")]
267 Amount(#[from] ParseAmountError),
268 #[error("Amount cannot be expressed in satoshis: {0}")]
270 Precision(#[from] AmountConversionError),
271}
272
273impl<'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#[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 pub txid: TransactionId,
358 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#[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 pub txid: TransactionId,
389 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#[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 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#[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 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 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 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
612pub 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
626pub 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
640pub 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
659pub 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
683pub 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;