Implement PartiallySignedTransaction::fee
to calculate fee if previous outputs are available.
This commit is contained in:
parent
1d0b721e5e
commit
c34d5f8f85
|
@ -74,6 +74,10 @@ pub enum Error {
|
|||
CombineInconsistentKeySources(Box<ExtendedPubKey>),
|
||||
/// Serialization error in bitcoin consensus-encoded structures
|
||||
ConsensusEncoding,
|
||||
/// Negative fee
|
||||
NegativeFee,
|
||||
/// Integer overflow in fee calculation
|
||||
FeeOverflow,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
|
@ -101,6 +105,9 @@ impl fmt::Display for Error {
|
|||
},
|
||||
Error::CombineInconsistentKeySources(ref s) => { write!(f, "combine conflict: {}", s) },
|
||||
Error::ConsensusEncoding => f.write_str("bitcoin consensus or BIP-174 encoding error"),
|
||||
Error::NegativeFee => f.write_str("PSBT has a negative fee which is not allowed"),
|
||||
Error::FeeOverflow => f.write_str("integer overflow in fee calculation"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +135,9 @@ impl std::error::Error for Error {
|
|||
| NonStandardSighashType(_)
|
||||
| InvalidPreimageHashPair{ .. }
|
||||
| CombineInconsistentKeySources(_)
|
||||
| ConsensusEncoding => None,
|
||||
| ConsensusEncoding
|
||||
| NegativeFee
|
||||
| FeeOverflow => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use core::ops::Deref;
|
|||
use secp256k1::{Message, Secp256k1, Signing};
|
||||
use bitcoin_internals::write_err;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{prelude::*, Amount};
|
||||
use crate::io;
|
||||
|
||||
use crate::blockdata::script::Script;
|
||||
|
@ -437,6 +437,28 @@ impl PartiallySignedTransaction {
|
|||
// because there has been a new softfork that we do not yet support.
|
||||
Err(SignError::UnknownOutputType)
|
||||
}
|
||||
|
||||
/// Calculates transaction fee.
|
||||
///
|
||||
/// 'Fee' being the amount that will be paid for mining a transaction with the current inputs
|
||||
/// and outputs i.e., the difference in value of the total inputs and the total outputs.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// - [`Error::MissingUtxo`] when UTXO information for any input is not present or is invalid.
|
||||
/// - [`Error::NegativeFee`] if calculated value is negative.
|
||||
/// - [`Error::FeeOverflow`] if an integer overflow occurs.
|
||||
pub fn fee(&self) -> Result<Amount, Error> {
|
||||
let mut inputs: u64 = 0;
|
||||
for utxo in self.iter_funding_utxos() {
|
||||
inputs = inputs.checked_add(utxo?.value).ok_or(Error::FeeOverflow)?;
|
||||
}
|
||||
let mut outputs: u64 = 0;
|
||||
for out in &self.unsigned_tx.output {
|
||||
outputs = outputs.checked_add(out.value).ok_or(Error::FeeOverflow)?;
|
||||
}
|
||||
inputs.checked_sub(outputs).map(Amount::from_sat).ok_or(Error::NegativeFee)
|
||||
}
|
||||
}
|
||||
|
||||
/// Data required to call [`GetKey`] to get the private key to sign an input.
|
||||
|
@ -1689,6 +1711,106 @@ mod tests {
|
|||
assert_eq!(got.unwrap(), priv_key)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fee() {
|
||||
let output_0_val = 99999699;
|
||||
let output_1_val = 100000000;
|
||||
let prev_output_val = 200000000;
|
||||
|
||||
let mut t = PartiallySignedTransaction {
|
||||
unsigned_tx: Transaction {
|
||||
version: 2,
|
||||
lock_time: absolute::PackedLockTime(1257139),
|
||||
input: vec![TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: Txid::from_hex(
|
||||
"f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126",
|
||||
).unwrap(),
|
||||
vout: 0,
|
||||
},
|
||||
sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
|
||||
..Default::default()
|
||||
}],
|
||||
output: vec![
|
||||
TxOut {
|
||||
value: output_0_val,
|
||||
..Default::default()
|
||||
},
|
||||
TxOut {
|
||||
value: output_1_val,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
},
|
||||
xpub: Default::default(),
|
||||
version: 0,
|
||||
proprietary: BTreeMap::new(),
|
||||
unknown: BTreeMap::new(),
|
||||
|
||||
inputs: vec![Input {
|
||||
non_witness_utxo: Some(Transaction {
|
||||
version: 1,
|
||||
lock_time: absolute::PackedLockTime::ZERO,
|
||||
input: vec![TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: Txid::from_hex(
|
||||
"e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389",
|
||||
).unwrap(),
|
||||
vout: 1,
|
||||
},
|
||||
sequence: Sequence::MAX,
|
||||
..Default::default()
|
||||
},
|
||||
TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: Txid::from_hex(
|
||||
"b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886",
|
||||
).unwrap(),
|
||||
vout: 1,
|
||||
},
|
||||
sequence: Sequence::MAX,
|
||||
..Default::default()
|
||||
}],
|
||||
output: vec![
|
||||
TxOut {
|
||||
value: prev_output_val,
|
||||
..Default::default()
|
||||
},
|
||||
TxOut {
|
||||
value: 190303501938,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
}),
|
||||
..Default::default()
|
||||
},],
|
||||
outputs: vec![
|
||||
Output {
|
||||
..Default::default()
|
||||
},
|
||||
Output {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
t.fee().expect("fee calculation"),
|
||||
Amount::from_sat(prev_output_val - (output_0_val + output_1_val))
|
||||
);
|
||||
// no previous output
|
||||
let mut t2 = t.clone();
|
||||
t2.inputs[0].non_witness_utxo = None;
|
||||
assert_eq!(t2.fee(), Err(Error::MissingUtxo));
|
||||
// negative fee
|
||||
let mut t3 = t.clone();
|
||||
t3.unsigned_tx.output[0].value = prev_output_val;
|
||||
assert_eq!(t3.fee(), Err(Error::NegativeFee));
|
||||
// overflow
|
||||
t.unsigned_tx.output[0].value = u64::max_value();
|
||||
t.unsigned_tx.output[1].value = u64::max_value();
|
||||
assert_eq!(t.fee(), Err(Error::FeeOverflow));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rand")]
|
||||
fn sign_psbt() {
|
||||
|
|
Loading…
Reference in New Issue