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
145#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone)]
147pub enum BitcoinAmountOrAll {
148 All,
149 Amount(bitcoin::Amount),
150}
151
152impl std::fmt::Display for BitcoinAmountOrAll {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 match self {
155 Self::All => write!(f, "all"),
156 Self::Amount(amount) => write!(f, "{amount}"),
157 }
158 }
159}
160
161impl FromStr for BitcoinAmountOrAll {
162 type Err = anyhow::Error;
163
164 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
165 if s.eq_ignore_ascii_case("all") {
166 Ok(Self::All)
167 } else {
168 let amount = Amount::from_str(s)?;
169 Ok(Self::Amount(amount.try_into()?))
170 }
171 }
172}
173
174impl<'de> Deserialize<'de> for BitcoinAmountOrAll {
176 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
177 where
178 D: Deserializer<'de>,
179 {
180 use serde::de::Error;
181
182 struct Visitor;
183
184 impl serde::de::Visitor<'_> for Visitor {
185 type Value = BitcoinAmountOrAll;
186
187 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
188 write!(f, "a bitcoin amount as number or 'all'")
189 }
190
191 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
192 where
193 E: Error,
194 {
195 if v.eq_ignore_ascii_case("all") {
196 Ok(BitcoinAmountOrAll::All)
197 } else {
198 let sat: u64 = v.parse().map_err(E::custom)?;
199 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(sat)))
200 }
201 }
202
203 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
204 where
205 E: Error,
206 {
207 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(v)))
208 }
209
210 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
211 where
212 E: Error,
213 {
214 if v < 0 {
215 return Err(E::custom("amount cannot be negative"));
216 }
217 Ok(BitcoinAmountOrAll::Amount(bitcoin::Amount::from_sat(
218 v as u64,
219 )))
220 }
221 }
222
223 deserializer.deserialize_any(Visitor)
224 }
225}
226
227impl Serialize for BitcoinAmountOrAll {
228 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229 where
230 S: Serializer,
231 {
232 match self {
233 Self::All => serializer.serialize_str("all"),
234 Self::Amount(a) => serializer.serialize_u64(a.to_sat()),
235 }
236 }
237}
238
239#[derive(
243 Debug,
244 Clone,
245 Copy,
246 Eq,
247 PartialEq,
248 PartialOrd,
249 Ord,
250 Hash,
251 Deserialize,
252 Serialize,
253 Encodable,
254 Decodable,
255)]
256pub struct InPoint {
257 pub txid: TransactionId,
259 pub in_idx: u64,
262}
263
264impl std::fmt::Display for InPoint {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 write!(f, "{}:{}", self.txid, self.in_idx)
267 }
268}
269
270#[derive(
274 Debug,
275 Clone,
276 Copy,
277 Eq,
278 PartialEq,
279 PartialOrd,
280 Ord,
281 Hash,
282 Deserialize,
283 Serialize,
284 Encodable,
285 Decodable,
286)]
287pub struct OutPoint {
288 pub txid: TransactionId,
290 pub out_idx: u64,
293}
294
295impl std::fmt::Display for OutPoint {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 write!(f, "{}:{}", self.txid, self.out_idx)
298 }
299}
300
301#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
303pub struct IdxRange {
304 start: u64,
305 end: u64,
306}
307
308impl IdxRange {
309 pub fn new_single(start: u64) -> Option<Self> {
310 start.checked_add(1).map(|end| Self { start, end })
311 }
312
313 pub fn start(self) -> u64 {
314 self.start
315 }
316
317 pub fn count(self) -> usize {
318 self.into_iter().count()
319 }
320
321 pub fn checked_count(self) -> Option<usize> {
329 usize::try_from(self.end.checked_sub(self.start)?).ok()
330 }
331
332 pub fn from_inclusive(range: ops::RangeInclusive<u64>) -> Option<Self> {
333 range.end().checked_add(1).map(|end| Self {
334 start: *range.start(),
335 end,
336 })
337 }
338}
339
340impl From<Range<u64>> for IdxRange {
341 fn from(Range { start, end }: Range<u64>) -> Self {
342 Self { start, end }
343 }
344}
345
346impl IntoIterator for IdxRange {
347 type Item = u64;
348 type IntoIter = ops::Range<u64>;
349
350 fn into_iter(self) -> Self::IntoIter {
351 ops::Range {
352 start: self.start,
353 end: self.end,
354 }
355 }
356}
357
358#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Encodable, Decodable)]
360pub struct OutPointRange {
361 pub txid: TransactionId,
362 idx_range: IdxRange,
363}
364
365impl OutPointRange {
366 pub fn new(txid: TransactionId, idx_range: IdxRange) -> Self {
367 Self { txid, idx_range }
368 }
369
370 pub fn new_single(txid: TransactionId, idx: u64) -> Option<Self> {
371 IdxRange::new_single(idx).map(|idx_range| Self { txid, idx_range })
372 }
373
374 pub fn start_idx(self) -> u64 {
375 self.idx_range.start()
376 }
377
378 pub fn out_idx_iter(self) -> impl Iterator<Item = u64> {
379 self.idx_range.into_iter()
380 }
381
382 pub fn count(self) -> usize {
383 self.idx_range.count()
384 }
385
386 pub fn checked_count(self) -> Option<usize> {
388 self.idx_range.checked_count()
389 }
390
391 pub fn txid(&self) -> TransactionId {
392 self.txid
393 }
394}
395
396impl IntoIterator for OutPointRange {
397 type Item = OutPoint;
398 type IntoIter = OutPointRangeIter;
399
400 fn into_iter(self) -> Self::IntoIter {
401 OutPointRangeIter {
402 txid: self.txid,
403 inner: self.idx_range.into_iter(),
404 }
405 }
406}
407
408pub struct OutPointRangeIter {
409 txid: TransactionId,
410 inner: ops::Range<u64>,
411}
412
413impl Iterator for OutPointRangeIter {
414 type Item = OutPoint;
415
416 fn next(&mut self) -> Option<Self::Item> {
417 self.inner.next().map(|idx| OutPoint {
418 txid: self.txid,
419 out_idx: idx,
420 })
421 }
422}
423
424impl Encodable for TransactionId {
425 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
426 let bytes = &self[..];
427 writer.write_all(bytes)?;
428 Ok(())
429 }
430}
431
432impl Decodable for TransactionId {
433 fn consensus_decode_partial<D: std::io::Read>(
434 d: &mut D,
435 _modules: &ModuleDecoderRegistry,
436 ) -> Result<Self, DecodeError> {
437 let mut bytes = [0u8; 32];
438 d.read_exact(&mut bytes).map_err(DecodeError::from_err)?;
439 Ok(Self::from_byte_array(bytes))
440 }
441}
442
443#[derive(
444 Copy,
445 Clone,
446 Debug,
447 PartialEq,
448 Ord,
449 PartialOrd,
450 Eq,
451 Hash,
452 Serialize,
453 Deserialize,
454 Encodable,
455 Decodable,
456)]
457pub struct Feerate {
458 pub sats_per_kvb: u64,
459}
460
461impl fmt::Display for Feerate {
462 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463 f.write_fmt(format_args!("{}sat/kvb", self.sats_per_kvb))
464 }
465}
466
467impl Feerate {
468 pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
469 let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
470 bitcoin::Amount::from_sat(sats)
471 }
472
473 pub fn wrapping_calculate_fee(&self, weight: u64) -> bitcoin::Amount {
481 let sats = weight_to_vbytes(weight).wrapping_mul(self.sats_per_kvb) / 1000;
482 bitcoin::Amount::from_sat(sats)
483 }
484
485 pub fn checked_calculate_fee(&self, weight: u64) -> Option<bitcoin::Amount> {
492 let sats = weight_to_vbytes(weight).checked_mul(self.sats_per_kvb)? / 1000;
493 (sats <= bitcoin::Amount::MAX_MONEY.to_sat()).then(|| bitcoin::Amount::from_sat(sats))
494 }
495}
496
497const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
498
499pub fn weight_to_vbytes(weight: u64) -> u64 {
504 weight.div_ceil(WITNESS_SCALE_FACTOR)
505}
506
507#[derive(Debug, Error)]
508pub enum CoreError {
509 #[error("Mismatching outcome variant: expected {0}, got {1}")]
510 MismatchingVariant(&'static str, &'static str),
511}
512
513pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
519 let mut feature_bytes = vec![];
520 for f in features.le_flags().iter().rev() {
521 f.write(&mut feature_bytes)
522 .expect("Writing to byte vec can't fail");
523 }
524 feature_bytes
525}
526
527pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
532 let prec = f.precision().unwrap_or(2 * data.len());
533 let width = f.width().unwrap_or(2 * data.len());
534 for _ in (2 * data.len())..width {
535 f.write_str("0")?;
536 }
537 for ch in data.iter().take(prec / 2) {
538 write!(f, "{:02x}", *ch)?;
539 }
540 if prec < 2 * data.len() && prec % 2 == 1 {
541 write!(f, "{:x}", data[prec / 2] / 16)?;
542 }
543 Ok(())
544}
545
546pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
559 if address.is_valid_for_network(Network::Bitcoin) {
560 Network::Bitcoin
561 } else if address.is_valid_for_network(Network::Testnet) {
562 Network::Testnet
563 } else if address.is_valid_for_network(Network::Regtest) {
564 Network::Regtest
565 } else {
566 panic!("Address is not valid for any network");
567 }
568}
569
570pub fn default_esplora_server(network: Network, port: Option<String>) -> BitcoinRpcConfig {
572 BitcoinRpcConfig {
573 kind: "esplora".to_string(),
574 url: match network {
575 Network::Bitcoin => SafeUrl::parse("https://mempool.space/api/"),
576 Network::Testnet => SafeUrl::parse("https://mempool.space/testnet/api/"),
577 Network::Testnet4 => SafeUrl::parse("https://mempool.space/testnet4/api/"),
578 Network::Signet => SafeUrl::parse("https://mutinynet.com/api/"),
579 Network::Regtest => SafeUrl::parse(&format!(
580 "http://127.0.0.1:{}/",
581 port.unwrap_or_else(|| String::from("50002"))
582 )),
583 _ => panic!("Failed to parse default esplora server"),
584 }
585 .expect("Failed to parse default esplora server"),
586 }
587}
588
589#[cfg(test)]
590mod tests;