Skip to content

Commit

Permalink
Improve transaction builder checks (#661)
Browse files Browse the repository at this point in the history
- Add should-panic checks to ensure they're operating correctly
- Add invariant messages to all expects and asserts
- Refactor build checks to operate on the built transaction, instead of the builder
  • Loading branch information
casey authored Oct 17, 2022
1 parent ca2e74a commit 208ba95
Showing 1 changed file with 152 additions and 27 deletions.
179 changes: 152 additions & 27 deletions src/subcommand/wallet/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,33 +110,10 @@ impl TransactionBuilder {
}

fn build(&self) -> Result<Transaction> {
let outpoint = self
.ranges
.iter()
.find(|(_outpoint, ranges)| {
ranges
.iter()
.any(|(start, end)| self.ordinal.0 >= *start && self.ordinal.0 < *end)
})
.expect("Could not find ordinal in utxo ranges");
let ordinal = self.ordinal.n();
let recipient = self.recipient.script_pubkey();

assert!(self.inputs.contains(outpoint.0));

let ordinal_offset = self.calculate_ordinal_offset();

let mut output_end = 0;
let mut found = false;
for output in &self.outputs {
output_end += output.1.to_sat();
if output_end > ordinal_offset {
assert_eq!(output.0, self.recipient);
found = true;
break;
}
}
assert!(found);

Ok(Transaction {
let transaction = Transaction {
version: 1,
lock_time: PackedLockTime::ZERO,
input: self
Expand All @@ -157,7 +134,61 @@ impl TransactionBuilder {
script_pubkey: address.script_pubkey(),
})
.collect(),
})
};

let outpoint = self
.ranges
.iter()
.find(|(_outpoint, ranges)| {
ranges
.iter()
.any(|(start, end)| ordinal >= *start && ordinal < *end)
})
.expect("invariant: ordinal is contained in utxo ranges");

assert_eq!(
transaction
.input
.iter()
.filter(|tx_in| tx_in.previous_output == *outpoint.0)
.count(),
1,
"invariant: inputs spend ordinal"
);

let mut ordinal_offset = 0;
let mut found = false;
for (start, end) in transaction
.input
.iter()
.flat_map(|tx_in| &self.ranges[&tx_in.previous_output])
{
if ordinal >= *start && ordinal < *end {
ordinal_offset += ordinal - start;
found = true;
break;
} else {
ordinal_offset += end - start;
}
}
assert!(found, "invariant: ordinal is found in inputs");

let mut output_end = 0;
let mut found = false;
for tx_out in &transaction.output {
output_end += tx_out.value;
if output_end > ordinal_offset {
assert_eq!(
tx_out.script_pubkey, recipient,
"invariant: ordinal is sent to recipient"
);
found = true;
break;
}
}
assert!(found, "invariant: ordinal is found in outputs");

Ok(transaction)
}

fn calculate_ordinal_offset(&self) -> u64 {
Expand Down Expand Up @@ -414,4 +445,98 @@ mod tests {
Err(Error::ConsumedByFee(Ordinal(14900)))
)
}

#[test]
#[should_panic(expected = "invariant: ordinal is contained in utxo ranges")]
fn invariant_ordinal_is_contained_in_utxo_ranges() {
TransactionBuilder::new(
[(
"1111111111111111111111111111111111111111111111111111111111111111:1"
.parse()
.unwrap(),
vec![(0, 2), (3, 5)],
)]
.into_iter()
.collect(),
Ordinal(2),
"tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz"
.parse()
.unwrap(),
)
.build()
.ok();
}

#[test]
#[should_panic(expected = "invariant: inputs spend ordinal")]
fn invariant_inputs_spend_ordinal() {
TransactionBuilder::new(
[(
"1111111111111111111111111111111111111111111111111111111111111111:1"
.parse()
.unwrap(),
vec![(0, 5)],
)]
.into_iter()
.collect(),
Ordinal(2),
"tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz"
.parse()
.unwrap(),
)
.build()
.ok();
}

#[test]
#[should_panic(expected = "invariant: ordinal is sent to recipient")]
fn invariant_ordinal_is_sent_to_recipient() {
let mut builder = TransactionBuilder::new(
[(
"1111111111111111111111111111111111111111111111111111111111111111:1"
.parse()
.unwrap(),
vec![(0, 5)],
)]
.into_iter()
.collect(),
Ordinal(2),
"tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz"
.parse()
.unwrap(),
)
.select_ordinal()
.unwrap();

builder.outputs[0].0 = "tb1qx4gf3ya0cxfcwydpq8vr2lhrysneuj5d7lqatw"
.parse()
.unwrap();

builder.build().ok();
}

#[test]
#[should_panic(expected = "invariant: ordinal is found in outputs")]
fn invariant_ordinal_is_found_in_outputs() {
let mut builder = TransactionBuilder::new(
[(
"1111111111111111111111111111111111111111111111111111111111111111:1"
.parse()
.unwrap(),
vec![(0, 5)],
)]
.into_iter()
.collect(),
Ordinal(2),
"tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz"
.parse()
.unwrap(),
)
.select_ordinal()
.unwrap();

builder.outputs[0].1 = Amount::from_sat(0);

builder.build().ok();
}
}

0 comments on commit 208ba95

Please sign in to comment.