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;
98pub mod peer_availability;
99mod peer_id;
101pub mod runtime;
103pub mod rustls;
105pub mod setup_code;
107pub mod task;
109pub mod tiered;
111pub mod tiered_multi;
113pub mod time;
115pub mod timing;
117pub mod transaction;
119pub mod txoproof;
121pub mod util;
123pub mod version;
125
126pub mod session_outcome;
128
129mod txid;
133pub use txid::TransactionId;
134
135pub struct TransactionIdShortFmt<'a>(&'a TransactionId);
136pub struct TransactionIdFullFmt<'a>(&'a TransactionId);
137
138impl TransactionId {
139 pub fn fmt_short(&self) -> TransactionIdShortFmt<'_> {
140 TransactionIdShortFmt(self)
141 }
142
143 pub fn fmt_full(&self) -> TransactionIdFullFmt<'_> {
144 TransactionIdFullFmt(self)
145 }
146}
147
148impl std::fmt::Display for TransactionIdShortFmt<'_> {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 let bytes = &self.0[..];
151 format_hex(&bytes[..4], f)?;
152 f.write_str("_")?;
153 format_hex(&bytes[28..], f)
154 }
155}
156
157impl std::fmt::Display for TransactionIdFullFmt<'_> {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 let bytes = &self.0[..];
160 format_hex(bytes, f)
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)]
175pub struct ChainId(pub bitcoin::BlockHash);
176
177impl ChainId {
178 pub fn new(block_hash: bitcoin::BlockHash) -> Self {
180 Self(block_hash)
181 }
182
183 pub fn block_hash(&self) -> bitcoin::BlockHash {
185 self.0
186 }
187}
188
189impl From<bitcoin::BlockHash> for ChainId {
190 fn from(block_hash: bitcoin::BlockHash) -> Self {
191 Self(block_hash)
192 }
193}
194
195impl From<ChainId> for bitcoin::BlockHash {
196 fn from(chain_id: ChainId) -> Self {
197 chain_id.0
198 }
199}
200
201impl std::fmt::Display for ChainId {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(f, "{}", self.0)
204 }
205}
206
207impl FromStr for ChainId {
208 type Err = bitcoin::hashes::hex::HexToArrayError;
209
210 fn from_str(s: &str) -> Result<Self, Self::Err> {
211 bitcoin::BlockHash::from_str(s).map(Self)
212 }
213}
214
215impl Serialize for ChainId {
216 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
217 where
218 S: Serializer,
219 {
220 self.0.serialize(serializer)
221 }
222}
223
224impl<'de> Deserialize<'de> for ChainId {
225 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
226 where
227 D: Deserializer<'de>,
228 {
229 bitcoin::BlockHash::deserialize(deserializer).map(Self)
230 }
231}
232
233#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone)]
235pub enum BitcoinAmountOrAll {
236 All,
237 Amount(bitcoin::Amount),
238}
239
240impl std::fmt::Display for BitcoinAmountOrAll {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 match self {
243 Self::All => write!(f, "all"),
244 Self::Amount(amount) => write!(f, "{amount}"),
245 }
246 }
247}
248
249impl FromStr for BitcoinAmountOrAll {
250 type Err = ParseBitcoinAmountOrAllError;
251
252 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
253 if s.eq_ignore_ascii_case("all") {
254 Ok(Self::All)
255 } else {
256 let amount = Amount::from_str(s)?;
257 Ok(Self::Amount(amount.try_into()?))
258 }
259 }
260}
261
262#[derive(Debug, Error)]
264#[non_exhaustive]
265pub enum ParseBitcoinAmountOrAllError {
266 #[error("Invalid amount: {0}")]
268 Amount(#[from] ParseAmountError),
269 #[error("Amount cannot be expressed in satoshis: {0}")]
271 Precision(#[from] AmountConversionError),
272}
273
274impl<'de> Deserialize<'de> for BitcoinAmountOrAll {
276 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
277 where
278 D: Deserializer<'de>,
279 {
280 use serde::de::Error;
281
282 struct Visitor;
283
284 impl serde::de::Visitor<'_> for Visitor {
285 type Value = BitcoinAmountOrAll;
286
287 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
288 write!(f, "a bitcoin amount as number or 'all'")
289 }
290
291 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
292 where
293 E: Error,
294 {
295 if v.eq_ignore_ascii_case("all") {
296 Ok(BitcoinAmountOrAll::All)
297 } else {
298 let sat: u64 = v.parse().map_err(E::custom)?;
299 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(sat)))
300 }
301 }
302
303 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
304 where
305 E: Error,
306 {
307 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(v)))
308 }
309
310 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
311 where
312 E: Error,
313 {
314 if v < 0 {
315 return Err(E::custom("amount cannot be negative"));
316 }
317 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(
318 v as u64,
319 )))
320 }
321 }
322
323 deserializer.deserialize_any(Visitor)
324 }
325}
326
327impl Serialize for BitcoinAmountOrAll {
328 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
329 where
330 S: Serializer,
331 {
332 match self {
333 Self::All => serializer.serialize_str("all"),
334 Self::Amount(a) => serializer.serialize_u64(a.to_sat()),
335 }
336 }
337}
338
339#[derive(
343 Debug,
344 Clone,
345 Copy,
346 Eq,
347 PartialEq,
348 PartialOrd,
349 Ord,
350 Hash,
351 Deserialize,
352 Serialize,
353 Encodable,
354 Decodable,
355)]
356pub struct InPoint {
357 pub txid: TransactionId,
359 pub in_idx: u64,
362}
363
364impl std::fmt::Display for InPoint {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 write!(f, "{}:{}", self.txid, self.in_idx)
367 }
368}
369
370#[derive(
374 Debug,
375 Clone,
376 Copy,
377 Eq,
378 PartialEq,
379 PartialOrd,
380 Ord,
381 Hash,
382 Deserialize,
383 Serialize,
384 Encodable,
385 Decodable,
386)]
387pub struct OutPoint {
388 pub txid: TransactionId,
390 pub out_idx: u64,
393}
394
395impl std::fmt::Display for OutPoint {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 write!(f, "{}:{}", self.txid, self.out_idx)
398 }
399}
400
401#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
403pub struct IdxRange {
404 start: u64,
405 end: u64,
406}
407
408impl IdxRange {
409 pub fn new_single(start: u64) -> Option<Self> {
410 start.checked_add(1).map(|end| Self { start, end })
411 }
412
413 pub fn start(self) -> u64 {
414 self.start
415 }
416
417 pub fn count(self) -> usize {
418 self.into_iter().count()
419 }
420
421 pub fn checked_count(self) -> Option<usize> {
429 usize::try_from(self.end.checked_sub(self.start)?).ok()
430 }
431
432 pub fn from_inclusive(range: ops::RangeInclusive<u64>) -> Option<Self> {
433 range.end().checked_add(1).map(|end| Self {
434 start: *range.start(),
435 end,
436 })
437 }
438}
439
440impl From<Range<u64>> for IdxRange {
441 fn from(Range { start, end }: Range<u64>) -> Self {
442 Self { start, end }
443 }
444}
445
446impl IntoIterator for IdxRange {
447 type Item = u64;
448 type IntoIter = ops::Range<u64>;
449
450 fn into_iter(self) -> Self::IntoIter {
451 ops::Range {
452 start: self.start,
453 end: self.end,
454 }
455 }
456}
457
458#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
460pub struct OutPointRange {
461 pub txid: TransactionId,
462 idx_range: IdxRange,
463}
464
465impl OutPointRange {
466 pub fn new(txid: TransactionId, idx_range: IdxRange) -> Self {
467 Self { txid, idx_range }
468 }
469
470 pub fn new_single(txid: TransactionId, idx: u64) -> Option<Self> {
471 IdxRange::new_single(idx).map(|idx_range| Self { txid, idx_range })
472 }
473
474 pub fn start_idx(self) -> u64 {
475 self.idx_range.start()
476 }
477
478 pub fn out_idx_iter(self) -> impl Iterator<Item = u64> {
479 self.idx_range.into_iter()
480 }
481
482 pub fn count(self) -> usize {
483 self.idx_range.count()
484 }
485
486 pub fn checked_count(self) -> Option<usize> {
488 self.idx_range.checked_count()
489 }
490
491 pub fn start_out_point(self) -> OutPoint {
492 OutPoint {
493 txid: self.txid,
494 out_idx: self.idx_range.start(),
495 }
496 }
497
498 pub fn end_out_point(self) -> OutPoint {
499 OutPoint {
500 txid: self.txid,
501 out_idx: self.idx_range.end,
502 }
503 }
504
505 pub fn txid(&self) -> TransactionId {
506 self.txid
507 }
508}
509
510impl IntoIterator for OutPointRange {
511 type Item = OutPoint;
512 type IntoIter = OutPointRangeIter;
513
514 fn into_iter(self) -> Self::IntoIter {
515 OutPointRangeIter {
516 txid: self.txid,
517 inner: self.idx_range.into_iter(),
518 }
519 }
520}
521
522pub struct OutPointRangeIter {
523 txid: TransactionId,
524 inner: ops::Range<u64>,
525}
526
527impl Iterator for OutPointRangeIter {
528 type Item = OutPoint;
529
530 fn next(&mut self) -> Option<Self::Item> {
531 self.inner.next().map(|idx| OutPoint {
532 txid: self.txid,
533 out_idx: idx,
534 })
535 }
536}
537
538impl Encodable for TransactionId {
539 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
540 let bytes = &self[..];
541 writer.write_all(bytes)?;
542 Ok(())
543 }
544}
545
546impl Decodable for TransactionId {
547 fn consensus_decode_partial<D: std::io::Read>(
548 d: &mut D,
549 _modules: &ModuleDecoderRegistry,
550 ) -> Result<Self, DecodeError> {
551 let mut bytes = [0u8; 32];
552 d.read_exact(&mut bytes)?;
553 Ok(Self::from_byte_array(bytes))
554 }
555}
556
557#[derive(
558 Copy,
559 Clone,
560 Debug,
561 PartialEq,
562 Ord,
563 PartialOrd,
564 Eq,
565 Hash,
566 Serialize,
567 Deserialize,
568 Encodable,
569 Decodable,
570)]
571pub struct Feerate {
572 pub sats_per_kvb: u64,
573}
574
575impl fmt::Display for Feerate {
576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577 f.write_fmt(format_args!("{}sat/kvb", self.sats_per_kvb))
578 }
579}
580
581impl Feerate {
582 pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
583 let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
584 bitcoin::Amount::from_sat(sats)
585 }
586
587 pub fn wrapping_calculate_fee(&self, weight: u64) -> bitcoin::Amount {
595 let sats = weight_to_vbytes(weight).wrapping_mul(self.sats_per_kvb) / 1000;
596 bitcoin::Amount::from_sat(sats)
597 }
598
599 pub fn checked_calculate_fee(&self, weight: u64) -> Option<bitcoin::Amount> {
606 let sats = weight_to_vbytes(weight).checked_mul(self.sats_per_kvb)? / 1000;
607 (sats <= bitcoin::Amount::MAX_MONEY.to_sat()).then(|| bitcoin::Amount::from_sat(sats))
608 }
609}
610
611const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
612
613pub fn weight_to_vbytes(weight: u64) -> u64 {
618 weight.div_ceil(WITNESS_SCALE_FACTOR)
619}
620
621#[derive(Debug, Error)]
622pub enum CoreError {
623 #[error("Mismatching outcome variant: expected {0}, got {1}")]
624 MismatchingVariant(&'static str, &'static str),
625}
626
627pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
633 let mut feature_bytes = vec![];
634 for f in features.le_flags().iter().rev() {
635 f.write(&mut feature_bytes)
636 .expect("Writing to byte vec can't fail");
637 }
638 feature_bytes
639}
640
641pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
646 let prec = f.precision().unwrap_or(2 * data.len());
647 let width = f.width().unwrap_or(2 * data.len());
648 for _ in (2 * data.len())..width {
649 f.write_str("0")?;
650 }
651 for ch in data.iter().take(prec / 2) {
652 write!(f, "{:02x}", *ch)?;
653 }
654 if prec < 2 * data.len() && prec % 2 == 1 {
655 write!(f, "{:x}", data[prec / 2] / 16)?;
656 }
657 Ok(())
658}
659
660pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
673 if address.is_valid_for_network(Network::Bitcoin) {
674 Network::Bitcoin
675 } else if address.is_valid_for_network(Network::Testnet) {
676 Network::Testnet
677 } else if address.is_valid_for_network(Network::Regtest) {
678 Network::Regtest
679 } else {
680 panic!("Address is not valid for any network");
681 }
682}
683
684pub fn default_esplora_server(network: Network, port: Option<String>) -> BitcoinRpcConfig {
686 BitcoinRpcConfig {
687 kind: "esplora".to_string(),
688 url: match network {
689 Network::Bitcoin => SafeUrl::parse("https://mempool.space/api/"),
690 Network::Testnet => SafeUrl::parse("https://mempool.space/testnet/api/"),
691 Network::Testnet4 => SafeUrl::parse("https://mempool.space/testnet4/api/"),
692 Network::Signet => SafeUrl::parse("https://mutinynet.com/api/"),
693 Network::Regtest => SafeUrl::parse(&format!(
694 "http://127.0.0.1:{}/",
695 port.unwrap_or_else(|| String::from("50002"))
696 )),
697 }
698 .expect("Failed to parse default esplora server"),
699 }
700}
701
702#[cfg(test)]
703mod tests;