psbt_v2/v2/
extract.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Implementation of the Extractor role as defined in [BIP-174].
4//!
5//! # Extractor Role
6//!
7//! > The Transaction Extractor does not need to know how to interpret scripts in order
8//! > to extract the network serialized transaction.
9//!
10//! It is only possible to extract a transaction from a PSBT _after_ it has been finalized. However
11//! the Extractor role may be fulfilled by a separate entity to the Finalizer hence this is a
12//! separate module and does not require the "miniscript" feature be enabled.
13//!
14//! [BIP-174]: <https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki>
15
16use core::fmt;
17
18use bitcoin::{FeeRate, Transaction, Txid};
19
20use crate::error::{write_err, FeeError};
21use crate::v2::{DetermineLockTimeError, Psbt};
22
23/// Implements the BIP-370 Finalized role.
24#[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    /// Creates an `Extractor`.
31    ///
32    /// An extractor can only accept a PSBT that has been finalized.
33    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    /// Returns this PSBT's unique identification.
43    pub fn id(&self) -> Txid {
44        self.0.id().expect("Extractor guarantees lock time can be determined")
45    }
46}
47
48impl Extractor {
49    /// The default `max_fee_rate` value used for extracting transactions with [`Self::extract_tx`].
50    ///
51    /// As of 2023, even the biggest overpayers during the highest fee markets only paid around
52    /// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point.
53    pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_unchecked(25_000);
54
55    /// An alias for [`Self::extract_tx_fee_rate_limit`].
56    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    /// Extracts the [`Transaction`] from a [`Psbt`] by filling in the available signature information.
61    ///
62    /// ## Errors
63    ///
64    /// `ExtractTxError` variants will contain either the [`Psbt`] itself or the [`Transaction`]
65    /// that was extracted. These can be extracted from the Errors in order to recover.
66    /// See the error documentation for info on the variants. In general, it covers large fees.
67    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    /// Extracts the [`Transaction`] from a [`Psbt`] by filling in the available signature information.
72    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    /// Perform [`Self::extract_tx_fee_rate_limit`] without the fee rate check.
80    ///
81    /// This can result in a transaction with absurdly high fees. Use with caution.
82    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        // Now that the extracted Transaction is made, decide how to return it.
95        let fee_rate =
96            FeeRate::from_sat_per_kwu(fee.to_sat().saturating_mul(1000) / tx.weight().to_wu());
97        // Prefer to return an AbsurdFeeRate error when both trigger.
98        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    /// Extracts a finalized transaction from the [`Psbt`].
106    ///
107    /// Uses `miniscript` to do interpreter checks.
108    #[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/// Error constructing an `Extractor`.
128#[derive(Debug)]
129pub enum ExtractError {
130    /// Attempted to extract tx from an unfinalized PSBT.
131    PsbtNotFinalized,
132    /// Finalizer must be able to determine the lock time.
133    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/// Error caused by fee calculation when extracting a [`Transaction`] from a PSBT.
165#[derive(Debug, Clone, PartialEq, Eq)]
166#[non_exhaustive]
167pub enum ExtractTxFeeRateError {
168    /// Error calculating the fee rate.
169    Fee(FeeError),
170    /// The calculated fee rate exceeds max.
171    FeeTooHigh {
172        /// Calculated fee.
173        fee: FeeRate,
174        /// Maximum allowable fee.
175        max: FeeRate,
176    },
177    /// Error extracting the transaction.
178    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/// Error extracting a [`Transaction`] from a PSBT.
215#[derive(Debug, Clone, PartialEq, Eq)]
216#[non_exhaustive]
217pub enum ExtractTxError {
218    /// Attempted to extract transaction from an unfinalized PSBT.
219    Unfinalized,
220    /// Failed to determine lock time.
221    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}