1use core::fmt;
17
18use bitcoin::{FeeRate, Transaction, Txid};
19
20use crate::error::{write_err, FeeError};
21use crate::v2::{DetermineLockTimeError, Psbt};
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
27pub struct Extractor(Psbt);
28
29impl Extractor {
30 pub fn new(psbt: Psbt) -> Result<Self, ExtractError> {
34 if psbt.inputs.iter().any(|input| !input.is_finalized()) {
35 return Err(ExtractError::PsbtNotFinalized);
36 }
37 let _ = psbt.determine_lock_time()?;
38
39 Ok(Self(psbt))
40 }
41
42 pub fn id(&self) -> Txid {
44 self.0.id().expect("Extractor guarantees lock time can be determined")
45 }
46}
47
48impl Extractor {
49 pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_unchecked(25_000);
54
55 pub fn extract_tx(&self) -> Result<Transaction, ExtractTxFeeRateError> {
57 self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE)
58 }
59
60 pub fn extract_tx_fee_rate_limit(&self) -> Result<Transaction, ExtractTxFeeRateError> {
68 self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE)
69 }
70
71 pub fn extract_tx_with_fee_rate_limit(
73 &self,
74 max_fee_rate: FeeRate,
75 ) -> Result<Transaction, ExtractTxFeeRateError> {
76 self.internal_extract_tx_with_fee_rate_limit(max_fee_rate)
77 }
78
79 pub fn extract_tx_unchecked_fee_rate(&self) -> Result<Transaction, ExtractTxError> {
83 self.internal_extract_tx()
84 }
85
86 #[inline]
87 fn internal_extract_tx_with_fee_rate_limit(
88 &self,
89 max_fee_rate: FeeRate,
90 ) -> Result<Transaction, ExtractTxFeeRateError> {
91 let fee = self.0.fee()?;
92 let tx = self.internal_extract_tx()?;
93
94 let fee_rate =
96 FeeRate::from_sat_per_kwu(fee.to_sat().saturating_mul(1000) / tx.weight().to_wu());
97 if fee_rate > max_fee_rate {
99 return Err(ExtractTxFeeRateError::FeeTooHigh { fee: fee_rate, max: max_fee_rate });
100 }
101
102 Ok(tx)
103 }
104
105 #[inline]
109 fn internal_extract_tx(&self) -> Result<Transaction, ExtractTxError> {
110 if !self.0.is_finalized() {
111 return Err(ExtractTxError::Unfinalized);
112 }
113
114 let lock_time = self.0.determine_lock_time()?;
115
116 let tx = Transaction {
117 version: self.0.global.tx_version,
118 lock_time,
119 input: self.0.inputs.iter().map(|input| input.signed_tx_in()).collect(),
120 output: self.0.outputs.iter().map(|ouput| ouput.tx_out()).collect(),
121 };
122
123 Ok(tx)
124 }
125}
126
127#[derive(Debug)]
129pub enum ExtractError {
130 PsbtNotFinalized,
132 DetermineLockTime(DetermineLockTimeError),
134}
135
136impl fmt::Display for ExtractError {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 use ExtractError::*;
139
140 match *self {
141 PsbtNotFinalized => write!(f, "attempted to extract tx from an unfinalized PSBT"),
142 DetermineLockTime(ref e) =>
143 write_err!(f, "extractor must be able to determine the lock time"; e),
144 }
145 }
146}
147
148#[cfg(feature = "std")]
149impl std::error::Error for ExtractError {
150 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
151 use ExtractError::*;
152
153 match *self {
154 DetermineLockTime(ref e) => Some(e),
155 PsbtNotFinalized => None,
156 }
157 }
158}
159
160impl From<DetermineLockTimeError> for ExtractError {
161 fn from(e: DetermineLockTimeError) -> Self { Self::DetermineLockTime(e) }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
166#[non_exhaustive]
167pub enum ExtractTxFeeRateError {
168 Fee(FeeError),
170 FeeTooHigh {
172 fee: FeeRate,
174 max: FeeRate,
176 },
177 ExtractTx(ExtractTxError),
179}
180
181impl fmt::Display for ExtractTxFeeRateError {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 use ExtractTxFeeRateError::*;
184
185 match *self {
186 Fee(ref e) => write_err!(f, "fee calculation"; e),
187 FeeTooHigh { fee, max } => write!(f, "fee {} is greater than max {}", fee, max),
188 ExtractTx(ref e) => write_err!(f, "extract"; e),
189 }
190 }
191}
192
193#[cfg(feature = "std")]
194impl std::error::Error for ExtractTxFeeRateError {
195 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
196 use ExtractTxFeeRateError::*;
197
198 match *self {
199 Fee(ref e) => Some(e),
200 ExtractTx(ref e) => Some(e),
201 FeeTooHigh { .. } => None,
202 }
203 }
204}
205
206impl From<FeeError> for ExtractTxFeeRateError {
207 fn from(e: FeeError) -> Self { Self::Fee(e) }
208}
209
210impl From<ExtractTxError> for ExtractTxFeeRateError {
211 fn from(e: ExtractTxError) -> Self { Self::ExtractTx(e) }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216#[non_exhaustive]
217pub enum ExtractTxError {
218 Unfinalized,
220 DetermineLockTime(DetermineLockTimeError),
222}
223
224impl fmt::Display for ExtractTxError {
225 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { todo!() }
226}
227
228#[cfg(feature = "std")]
229impl std::error::Error for ExtractTxError {
230 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { todo!() }
231}
232
233impl From<DetermineLockTimeError> for ExtractTxError {
234 fn from(e: DetermineLockTimeError) -> Self { Self::DetermineLockTime(e) }
235}