Skip to main content

bitcoin_ext/
lib.rs

1
2#[macro_use] extern crate lazy_static;
3#[macro_use] extern crate serde as serde_crate;
4
5pub extern crate bitcoin;
6
7pub mod cpfp;
8pub mod fee;
9
10#[cfg(feature = "bdk")]
11pub mod bdk;
12#[cfg(feature = "rpc")]
13pub mod rpc;
14pub mod serde;
15
16pub use mbitcoin::{
17	AddressExt, AmountExt, FeeRateExt, NonStandardOutput, TaprootSpendInfoExt, KeypairExt,
18	TransactionExt, TxOutExt,
19};
20
21#[path = "bitcoin.rs"]
22mod mbitcoin;
23
24use std::{fmt, str::FromStr};
25
26use bitcoin::{Amount, BlockHash, Weight};
27
28use serde_crate::ser::SerializeStruct;
29
30/// The number of confirmations after which we don't expect a
31/// re-org to ever happen.
32pub const DEEPLY_CONFIRMED: BlockHeight = 100;
33
34pub const P2TR_DUST_VB: u64 = 110;
35/// 330 satoshis
36pub const P2TR_DUST_SAT: u64 = P2TR_DUST_VB * 3;
37pub const P2TR_DUST: Amount = Amount::from_sat(P2TR_DUST_SAT);
38
39pub const P2WPKH_DUST_VB: u64 = 90;
40/// 294 satoshis
41pub const P2WPKH_DUST_SAT: u64 = P2WPKH_DUST_VB * 3;
42pub const P2WPKH_DUST: Amount = Amount::from_sat(P2WPKH_DUST_SAT);
43
44pub const P2PKH_DUST_VB: u64 = 182;
45/// 546 satoshis
46pub const P2PKH_DUST_SAT: u64 = P2PKH_DUST_VB * 3;
47pub const P2PKH_DUST: Amount = Amount::from_sat(P2PKH_DUST_SAT);
48
49pub const P2SH_DUST_VB: u64 = 180;
50/// 540 satoshis
51pub const P2SH_DUST_SAT: u64 = P2SH_DUST_VB * 3;
52pub const P2SH_DUST: Amount = Amount::from_sat(P2SH_DUST_SAT);
53
54pub const P2WSH_DUST_VB: u64 = 110;
55/// 330 satoshis
56pub const P2WSH_DUST_SAT: u64 = P2WSH_DUST_VB * 3;
57pub const P2WSH_DUST: Amount = Amount::from_sat(P2WSH_DUST_SAT);
58
59/// Witness weight of a taproot keyspend.
60pub const TAPROOT_KEYSPEND_WEIGHT: Weight = Weight::from_wu(66);
61
62/// The maximum standard tx weight
63pub const MAX_TX_WEIGHT: Weight = Weight::from_wu(400_000);
64
65/// Type representing a block height in the bitcoin blockchain.
66pub type BlockHeight = u32;
67/// Type representing a block height delta
68pub type BlockDelta = u16;
69/// Reference to a block in the chain
70///
71/// String representation is `<height>:<hash>`.
72#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
73pub struct BlockRef {
74	pub height: BlockHeight,
75	pub hash: BlockHash,
76}
77
78impl fmt::Display for BlockRef {
79	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80		write!(f, "{}:{}", self.height, self.hash)
81	}
82}
83
84impl fmt::Debug for BlockRef {
85	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86		fmt::Display::fmt(self, f)
87	}
88}
89
90impl FromStr for BlockRef {
91	type Err = &'static str;
92
93	fn from_str(s: &str) -> Result<Self, Self::Err> {
94		let mut parts = s.splitn(2, ':');
95		Ok(BlockRef {
96			height: parts.next().expect("always one part")
97				.parse().map_err(|_| "invalid height")?,
98			hash: parts.next().ok_or("should be <height>:<hash> string")?
99				.parse().map_err(|_| "invalid hash")?,
100		})
101	}
102}
103
104impl serde_crate::Serialize for BlockRef {
105	fn serialize<S: serde_crate::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
106		let mut state = s.serialize_struct("BlockRef", 2)?;
107		state.serialize_field("height", &self.height)?;
108		state.serialize_field("hash", &self.hash)?;
109		state.end()
110	}
111}
112
113impl<'de> serde_crate::Deserialize<'de> for BlockRef {
114	fn deserialize<D: serde_crate::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
115		struct Visitor;
116		impl<'de> serde_crate::de::Visitor<'de> for Visitor {
117			type Value = BlockRef;
118			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119				write!(f, "a BlockRef (struct/string)")
120			}
121			fn visit_str<E: serde_crate::de::Error>(self, v: &str) -> Result<Self::Value, E> {
122				BlockRef::from_str(v).map_err(serde_crate::de::Error::custom)
123			}
124			fn visit_map<A: serde_crate::de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
125				let mut height = None;
126				let mut hash = None;
127				while let Some(key) = map.next_key::<&str>()? {
128					match key {
129						"height" => height = Some(map.next_value()?),
130						"hash" => hash = Some(map.next_value()?),
131						_ => {
132							let _ = map.next_value::<serde_crate::de::IgnoredAny>()?;
133						}
134					}
135				}
136				Ok(BlockRef {
137					height: height.ok_or_else(|| serde_crate::de::Error::missing_field("height"))?,
138					hash: hash.ok_or_else(|| serde_crate::de::Error::missing_field("hash"))?,
139				})
140			}
141		}
142		d.deserialize_any(Visitor)
143	}
144}
145
146#[cfg(feature = "bdk")]
147impl From<bdk_wallet::chain::BlockId> for BlockRef {
148	fn from(id: bdk_wallet::chain::BlockId) -> Self {
149		Self {
150			height: id.height,
151			hash: id.hash,
152		}
153	}
154}
155
156#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
157pub enum TxStatus {
158	Confirmed(BlockRef),
159	Mempool,
160	NotFound,
161}
162
163impl TxStatus {
164	pub fn is_confirmed(&self) -> bool {
165		match self {
166			TxStatus::Confirmed(_) => true,
167			_ => false,
168		}
169	}
170
171	pub fn confirmed_height(&self) -> Option<BlockHeight> {
172		match self {
173			TxStatus::Confirmed(block_ref) => Some(block_ref.height),
174			_ => None,
175		}
176	}
177
178	pub fn confirmed_in(&self) -> Option<BlockRef> {
179		match self {
180			TxStatus::Confirmed(block_ref) => Some(*block_ref),
181			_ => None,
182		}
183	}
184
185	pub fn is_known(&self) -> bool {
186		match self {
187			TxStatus::Confirmed(..) | TxStatus::Mempool => true,
188			TxStatus::NotFound => false,
189		}
190	}
191}
192
193#[cfg(test)]
194mod test {
195	use super::*;
196
197	#[test]
198	fn tx_status_is_known() {
199		let block = BlockRef {
200			height: 42,
201			hash: "000000000000000000024e0e2d3a1b03bb6e39b1e79b3b4b6e30e7bd39cd6f6f"
202				.parse().unwrap(),
203		};
204		// A tx we can see, confirmed or not, is known.
205		assert!(TxStatus::Confirmed(block).is_known());
206		assert!(TxStatus::Mempool.is_known());
207		// Only a tx we've never seen isn't.
208		assert!(!TxStatus::NotFound.is_known());
209	}
210}
211