Skip to content

Commit

Permalink
Check transaction fees in transaction builder (#669)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Oct 19, 2022
1 parent a9923d9 commit d9a050f
Showing 1 changed file with 84 additions and 7 deletions.
91 changes: 84 additions & 7 deletions src/subcommand/wallet/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ pub(crate) struct TransactionBuilder {
type Result<T> = std::result::Result<T, Error>;

impl TransactionBuilder {
const TARGET_POSTAGE: Amount = Amount::from_sat(10_000);
const MAX_POSTAGE: Amount = Amount::from_sat(2 * 10_000);
const FEE_RATE: usize = 1;
const TARGET_FEE_RATE: Amount = Amount::from_sat(1);
const TARGET_POSTAGE: Amount = Amount::from_sat(10_000);

pub(crate) fn build_transaction(
ranges: BTreeMap<OutPoint, Vec<(u64, u64)>>,
Expand Down Expand Up @@ -171,8 +171,30 @@ impl TransactionBuilder {
fn deduct_fee(mut self) -> Result<Self> {
let ordinal_offset = self.calculate_ordinal_offset();

let tx = self.build()?;
let fee = Amount::from_sat((Self::FEE_RATE * tx.vsize()).try_into().unwrap());
let dummy_transaction = Transaction {
version: 1,
lock_time: PackedLockTime::ZERO,
input: self
.inputs
.iter()
.map(|_| TxIn {
previous_output: OutPoint::null(),
script_sig: Script::new(),
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
witness: Witness::new(),
})
.collect(),
output: self
.outputs
.iter()
.map(|(address, amount)| TxOut {
value: amount.to_sat(),
script_pubkey: address.script_pubkey(),
})
.collect(),
};

let fee = Self::TARGET_FEE_RATE * dummy_transaction.vsize().try_into().unwrap();

let total_output_amount = self
.outputs
Expand All @@ -194,7 +216,7 @@ impl TransactionBuilder {
Ok(self)
}

fn build(&self) -> Result<Transaction> {
fn build(self) -> Result<Transaction> {
let ordinal = self.ordinal.n();
let recipient = self.recipient.script_pubkey();
let transaction = Transaction {
Expand Down Expand Up @@ -287,6 +309,28 @@ impl TransactionBuilder {
offset += output.value;
}

let mut fee = Amount::ZERO;
for input in &transaction.input {
fee += Amount::from_sat(
self.ranges[&input.previous_output]
.iter()
.map(|(start, end)| end - start)
.sum::<u64>(),
);
}
for output in &transaction.output {
fee -= Amount::from_sat(output.value);
}

let fee_rate = fee.to_sat() as f64 / transaction.vsize() as f64;
let target_fee_rate = Self::TARGET_FEE_RATE.to_sat() as f64;
assert!(
fee_rate == target_fee_rate,
"invariant: fee rate is equal to target fee rate: actual fee rate: {} target_fee rate: {}",
fee_rate,
target_fee_rate,
);

Ok(transaction)
}

Expand Down Expand Up @@ -438,7 +482,7 @@ mod tests {
"tb1qakxxzv9n7706kc3xdcycrtfv8cqv62hnwexc0l"
.parse()
.unwrap(),
Amount::from_sat(2000),
Amount::from_sat(1774),
),
],
};
Expand Down Expand Up @@ -490,7 +534,7 @@ mod tests {
.script_pubkey(),
},
TxOut {
value: 2000,
value: 1774,
script_pubkey: "tb1qakxxzv9n7706kc3xdcycrtfv8cqv62hnwexc0l"
.parse::<Address>()
.unwrap()
Expand Down Expand Up @@ -880,4 +924,37 @@ mod tests {
.build()
.unwrap();
}

#[test]
#[should_panic(expected = "invariant: fee rate is equal to target fee rate")]
fn fee_is_at_least_target_fee_rate() {
let utxos = vec![(
"1111111111111111111111111111111111111111111111111111111111111111:1"
.parse()
.unwrap(),
vec![(0, 10_000)],
)];

TransactionBuilder::new(
utxos.into_iter().collect(),
Ordinal(0),
"tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz"
.parse()
.unwrap(),
vec![
"tb1qjsv26lap3ffssj6hfy8mzn0lg5vte6a42j75ww"
.parse()
.unwrap(),
"tb1qakxxzv9n7706kc3xdcycrtfv8cqv62hnwexc0l"
.parse()
.unwrap(),
],
)
.select_ordinal()
.unwrap()
.strip_excess_postage()
.unwrap()
.build()
.unwrap();
}
}

0 comments on commit d9a050f

Please sign in to comment.