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::*;
47pub use anyhow;
49use bitcoin::address::NetworkUnchecked;
50pub use bitcoin::hashes::Hash as BitcoinHash;
51use bitcoin::{Address, Network};
52use envs::BitcoinRpcConfig;
53use lightning::util::ser::Writeable;
54use lightning_types::features::Bolt11InvoiceFeatures;
55pub use macro_rules_attribute::apply;
56pub use peer_id::*;
57use serde::{Deserialize, Deserializer, Serialize, Serializer};
58use thiserror::Error;
59pub use tiered::Tiered;
60pub use tiered_multi::*;
61use util::SafeUrl;
62pub use {bitcoin, hex, secp256k1};
63
64use crate::encoding::{Decodable, DecodeError, Encodable};
65use crate::module::registry::ModuleDecoderRegistry;
66
67pub mod admin_client;
69mod amount;
71pub mod backup;
73pub mod bls12_381_serde;
75pub mod config;
77pub mod core;
79pub mod db;
81pub mod encoding;
83pub mod endpoint_constants;
84pub mod envs;
86pub mod epoch;
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
145pub struct TransactionIdShortFmt<'a>(&'a TransactionId);
146pub struct TransactionIdFullFmt<'a>(&'a TransactionId);
147
148impl TransactionId {
149 pub fn fmt_short(&self) -> TransactionIdShortFmt<'_> {
150 TransactionIdShortFmt(self)
151 }
152
153 pub fn fmt_full(&self) -> TransactionIdFullFmt<'_> {
154 TransactionIdFullFmt(self)
155 }
156}
157
158impl std::fmt::Display for TransactionIdShortFmt<'_> {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 let bytes = &self.0[..];
161 format_hex(&bytes[..4], f)?;
162 f.write_str("_")?;
163 format_hex(&bytes[28..], f)
164 }
165}
166
167impl std::fmt::Display for TransactionIdFullFmt<'_> {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 let bytes = &self.0[..];
170 format_hex(bytes, f)
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)]
185pub struct ChainId(pub bitcoin::BlockHash);
186
187impl ChainId {
188 pub fn new(block_hash: bitcoin::BlockHash) -> Self {
190 Self(block_hash)
191 }
192
193 pub fn block_hash(&self) -> bitcoin::BlockHash {
195 self.0
196 }
197}
198
199impl From<bitcoin::BlockHash> for ChainId {
200 fn from(block_hash: bitcoin::BlockHash) -> Self {
201 Self(block_hash)
202 }
203}
204
205impl From<ChainId> for bitcoin::BlockHash {
206 fn from(chain_id: ChainId) -> Self {
207 chain_id.0
208 }
209}
210
211impl std::fmt::Display for ChainId {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 write!(f, "{}", self.0)
214 }
215}
216
217impl FromStr for ChainId {
218 type Err = bitcoin::hashes::hex::HexToArrayError;
219
220 fn from_str(s: &str) -> Result<Self, Self::Err> {
221 bitcoin::BlockHash::from_str(s).map(Self)
222 }
223}
224
225impl Serialize for ChainId {
226 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
227 where
228 S: Serializer,
229 {
230 self.0.serialize(serializer)
231 }
232}
233
234impl<'de> Deserialize<'de> for ChainId {
235 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
236 where
237 D: Deserializer<'de>,
238 {
239 bitcoin::BlockHash::deserialize(deserializer).map(Self)
240 }
241}
242
243#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone)]
245pub enum BitcoinAmountOrAll {
246 All,
247 Amount(bitcoin::Amount),
248}
249
250impl std::fmt::Display for BitcoinAmountOrAll {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 match self {
253 Self::All => write!(f, "all"),
254 Self::Amount(amount) => write!(f, "{amount}"),
255 }
256 }
257}
258
259impl FromStr for BitcoinAmountOrAll {
260 type Err = anyhow::Error;
261
262 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
263 if s.eq_ignore_ascii_case("all") {
264 Ok(Self::All)
265 } else {
266 let amount = Amount::from_str(s)?;
267 Ok(Self::Amount(amount.try_into()?))
268 }
269 }
270}
271
272impl<'de> Deserialize<'de> for BitcoinAmountOrAll {
274 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
275 where
276 D: Deserializer<'de>,
277 {
278 use serde::de::Error;
279
280 struct Visitor;
281
282 impl serde::de::Visitor<'_> for Visitor {
283 type Value = BitcoinAmountOrAll;
284
285 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
286 write!(f, "a bitcoin amount as number or 'all'")
287 }
288
289 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
290 where
291 E: Error,
292 {
293 if v.eq_ignore_ascii_case("all") {
294 Ok(BitcoinAmountOrAll::All)
295 } else {
296 let sat: u64 = v.parse().map_err(E::custom)?;
297 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(sat)))
298 }
299 }
300
301 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
302 where
303 E: Error,
304 {
305 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(v)))
306 }
307
308 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
309 where
310 E: Error,
311 {
312 if v < 0 {
313 return Err(E::custom("amount cannot be negative"));
314 }
315 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(
316 v as u64,
317 )))
318 }
319 }
320
321 deserializer.deserialize_any(Visitor)
322 }
323}
324
325impl Serialize for BitcoinAmountOrAll {
326 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
327 where
328 S: Serializer,
329 {
330 match self {
331 Self::All => serializer.serialize_str("all"),
332 Self::Amount(a) => serializer.serialize_u64(a.to_sat()),
333 }
334 }
335}
336
337#[derive(
341 Debug,
342 Clone,
343 Copy,
344 Eq,
345 PartialEq,
346 PartialOrd,
347 Ord,
348 Hash,
349 Deserialize,
350 Serialize,
351 Encodable,
352 Decodable,
353)]
354pub struct InPoint {
355 pub txid: TransactionId,
357 pub in_idx: u64,
360}
361
362impl std::fmt::Display for InPoint {
363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364 write!(f, "{}:{}", self.txid, self.in_idx)
365 }
366}
367
368#[derive(
372 Debug,
373 Clone,
374 Copy,
375 Eq,
376 PartialEq,
377 PartialOrd,
378 Ord,
379 Hash,
380 Deserialize,
381 Serialize,
382 Encodable,
383 Decodable,
384)]
385pub struct OutPoint {
386 pub txid: TransactionId,
388 pub out_idx: u64,
391}
392
393impl std::fmt::Display for OutPoint {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 write!(f, "{}:{}", self.txid, self.out_idx)
396 }
397}
398
399#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
401pub struct IdxRange {
402 start: u64,
403 end: u64,
404}
405
406impl IdxRange {
407 pub fn new_single(start: u64) -> Option<Self> {
408 start.checked_add(1).map(|end| Self { start, end })
409 }
410
411 pub fn start(self) -> u64 {
412 self.start
413 }
414
415 pub fn count(self) -> usize {
416 self.into_iter().count()
417 }
418
419 pub fn from_inclusive(range: ops::RangeInclusive<u64>) -> Option<Self> {
420 range.end().checked_add(1).map(|end| Self {
421 start: *range.start(),
422 end,
423 })
424 }
425}
426
427impl From<Range<u64>> for IdxRange {
428 fn from(Range { start, end }: Range<u64>) -> Self {
429 Self { start, end }
430 }
431}
432
433impl IntoIterator for IdxRange {
434 type Item = u64;
435 type IntoIter = ops::Range<u64>;
436
437 fn into_iter(self) -> Self::IntoIter {
438 ops::Range {
439 start: self.start,
440 end: self.end,
441 }
442 }
443}
444
445#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
447pub struct OutPointRange {
448 pub txid: TransactionId,
449 idx_range: IdxRange,
450}
451
452impl OutPointRange {
453 pub fn new(txid: TransactionId, idx_range: IdxRange) -> Self {
454 Self { txid, idx_range }
455 }
456
457 pub fn new_single(txid: TransactionId, idx: u64) -> Option<Self> {
458 IdxRange::new_single(idx).map(|idx_range| Self { txid, idx_range })
459 }
460
461 pub fn start_idx(self) -> u64 {
462 self.idx_range.start()
463 }
464
465 pub fn out_idx_iter(self) -> impl Iterator<Item = u64> {
466 self.idx_range.into_iter()
467 }
468
469 pub fn count(self) -> usize {
470 self.idx_range.count()
471 }
472
473 pub fn start_out_point(self) -> OutPoint {
474 OutPoint {
475 txid: self.txid,
476 out_idx: self.idx_range.start(),
477 }
478 }
479
480 pub fn end_out_point(self) -> OutPoint {
481 OutPoint {
482 txid: self.txid,
483 out_idx: self.idx_range.end,
484 }
485 }
486
487 pub fn txid(&self) -> TransactionId {
488 self.txid
489 }
490}
491
492impl IntoIterator for OutPointRange {
493 type Item = OutPoint;
494 type IntoIter = OutPointRangeIter;
495
496 fn into_iter(self) -> Self::IntoIter {
497 OutPointRangeIter {
498 txid: self.txid,
499 inner: self.idx_range.into_iter(),
500 }
501 }
502}
503
504pub struct OutPointRangeIter {
505 txid: TransactionId,
506 inner: ops::Range<u64>,
507}
508
509impl Iterator for OutPointRangeIter {
510 type Item = OutPoint;
511
512 fn next(&mut self) -> Option<Self::Item> {
513 self.inner.next().map(|idx| OutPoint {
514 txid: self.txid,
515 out_idx: idx,
516 })
517 }
518}
519
520impl Encodable for TransactionId {
521 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
522 let bytes = &self[..];
523 writer.write_all(bytes)?;
524 Ok(())
525 }
526}
527
528impl Decodable for TransactionId {
529 fn consensus_decode_partial<D: std::io::Read>(
530 d: &mut D,
531 _modules: &ModuleDecoderRegistry,
532 ) -> Result<Self, DecodeError> {
533 let mut bytes = [0u8; 32];
534 d.read_exact(&mut bytes).map_err(DecodeError::from_err)?;
535 Ok(Self::from_byte_array(bytes))
536 }
537}
538
539#[derive(
540 Copy,
541 Clone,
542 Debug,
543 PartialEq,
544 Ord,
545 PartialOrd,
546 Eq,
547 Hash,
548 Serialize,
549 Deserialize,
550 Encodable,
551 Decodable,
552)]
553pub struct Feerate {
554 pub sats_per_kvb: u64,
555}
556
557impl fmt::Display for Feerate {
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 f.write_fmt(format_args!("{}sat/kvb", self.sats_per_kvb))
560 }
561}
562
563impl Feerate {
564 pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
565 let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
566 bitcoin::Amount::from_sat(sats)
567 }
568}
569
570const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
571
572pub fn weight_to_vbytes(weight: u64) -> u64 {
577 weight.div_ceil(WITNESS_SCALE_FACTOR)
578}
579
580#[derive(Debug, Error)]
581pub enum CoreError {
582 #[error("Mismatching outcome variant: expected {0}, got {1}")]
583 MismatchingVariant(&'static str, &'static str),
584}
585
586pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
592 let mut feature_bytes = vec![];
593 for f in features.le_flags().iter().rev() {
594 f.write(&mut feature_bytes)
595 .expect("Writing to byte vec can't fail");
596 }
597 feature_bytes
598}
599
600pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
605 let prec = f.precision().unwrap_or(2 * data.len());
606 let width = f.width().unwrap_or(2 * data.len());
607 for _ in (2 * data.len())..width {
608 f.write_str("0")?;
609 }
610 for ch in data.iter().take(prec / 2) {
611 write!(f, "{:02x}", *ch)?;
612 }
613 if prec < 2 * data.len() && prec % 2 == 1 {
614 write!(f, "{:x}", data[prec / 2] / 16)?;
615 }
616 Ok(())
617}
618
619pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
632 if address.is_valid_for_network(Network::Bitcoin) {
633 Network::Bitcoin
634 } else if address.is_valid_for_network(Network::Testnet) {
635 Network::Testnet
636 } else if address.is_valid_for_network(Network::Regtest) {
637 Network::Regtest
638 } else {
639 panic!("Address is not valid for any network");
640 }
641}
642
643pub fn default_esplora_server(network: Network, port: Option<String>) -> BitcoinRpcConfig {
645 BitcoinRpcConfig {
646 kind: "esplora".to_string(),
647 url: match network {
648 Network::Bitcoin => SafeUrl::parse("https://mempool.space/api/"),
649 Network::Testnet => SafeUrl::parse("https://mempool.space/testnet/api/"),
650 Network::Testnet4 => SafeUrl::parse("https://mempool.space/testnet4/api/"),
651 Network::Signet => SafeUrl::parse("https://mutinynet.com/api/"),
652 Network::Regtest => SafeUrl::parse(&format!(
653 "http://127.0.0.1:{}/",
654 port.unwrap_or_else(|| String::from("50002"))
655 )),
656 }
657 .expect("Failed to parse default esplora server"),
658 }
659}
660
661#[cfg(test)]
662mod tests;