Skip to content

Commit 3b457b3

Browse files
committed
fix(wallet): return error instead of panicking on invalid current_height
1 parent fbf803a commit 3b457b3

4 files changed

Lines changed: 36 additions & 7 deletions

File tree

src/wallet/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ pub enum CreateTxError {
215215
MissingNonWitnessUtxo(OutPoint),
216216
/// Miniscript PSBT error
217217
MiniscriptPsbt(MiniscriptPsbtError),
218+
/// Provided height is not a valid block height for use as a locktime
219+
///
220+
/// Block-based locktimes require a height below 500_000_000.
221+
InvalidCurrentHeight(u32),
218222
}
219223

220224
impl fmt::Display for CreateTxError {
@@ -281,6 +285,12 @@ impl fmt::Display for CreateTxError {
281285
CreateTxError::MiniscriptPsbt(err) => {
282286
write!(f, "Miniscript PSBT error: {err}")
283287
}
288+
CreateTxError::InvalidCurrentHeight(height) => {
289+
write!(
290+
f,
291+
"Cannot use height {height} as a locktime (must be below 500_000_000)"
292+
)
293+
}
284294
}
285295
}
286296
}

src/wallet/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,13 +1303,13 @@ impl Wallet {
13031303
// We use a match here instead of a unwrap_or_else as it's way more readable :)
13041304
let current_height = match params.current_height {
13051305
// If they didn't tell us the current height, we assume it's the latest sync height.
1306-
None => {
1307-
let tip_height = self.chain.tip().height();
1308-
absolute::LockTime::from_height(tip_height).expect("invalid height")
1309-
}
1306+
None => self.chain.tip().height(),
13101307
Some(h) => h,
13111308
};
13121309

1310+
let current_height = absolute::LockTime::from_height(current_height)
1311+
.map_err(|_| CreateTxError::InvalidCurrentHeight(current_height))?;
1312+
13131313
let lock_time = match params.locktime {
13141314
// When no `nLockTime` is specified, we try to prevent fee sniping, if possible.
13151315
None => {

src/wallet/tx_builder.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ pub(crate) struct TxParams {
138138
pub(crate) only_witness_utxo: bool,
139139
pub(crate) add_global_xpubs: bool,
140140
pub(crate) bumping_fee: Option<PreviousFee>,
141-
pub(crate) current_height: Option<absolute::LockTime>,
141+
pub(crate) current_height: Option<u32>,
142142
pub(crate) allow_dust: bool,
143143
}
144144

@@ -650,8 +650,7 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
650650
///
651651
/// In both cases, if you don't provide a current height, we use the last sync height.
652652
pub fn current_height(&mut self, height: u32) -> &mut Self {
653-
self.params.current_height =
654-
Some(absolute::LockTime::from_height(height).expect("Invalid height"));
653+
self.params.current_height = Some(height);
655654
self
656655
}
657656

tests/wallet.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@ fn test_create_tx_custom_locktime() {
308308
assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000);
309309
}
310310

311+
#[test]
312+
fn test_create_tx_invalid_current_height_returns_error() {
313+
// `absolute::LockTime::from_height` rejects heights >= 500_000_000 because the
314+
// consensus encoding of nLockTime uses those values to mean "unix timestamp" rather
315+
// than "block height". Passing such a value to `TxBuilder::current_height` used to
316+
// panic; it should now surface as a typed error.
317+
let (mut wallet, _) = get_funded_wallet_wpkh();
318+
let addr = wallet.next_unused_address(KeychainKind::External);
319+
320+
let mut builder = wallet.build_tx();
321+
builder
322+
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
323+
.current_height(500_000_000);
324+
325+
assert_matches!(
326+
builder.finish(),
327+
Err(CreateTxError::InvalidCurrentHeight(500_000_000))
328+
);
329+
}
330+
311331
#[test]
312332
fn test_create_tx_custom_locktime_compatible_with_cltv() {
313333
let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv());

0 commit comments

Comments
 (0)