From: Vihiga Tyonum Date: Sat, 26 Sep 2026 07:56:45 +0000 (+0100) Subject: fix(create_tx,bump_fee): return errors instead of panicking X-Git-Url: http://internal-gitweb-vhost/-client-interface/src/example_cli/static/struct.ScriptBuf.html?a=commitdiff_plain;h=3e7db1b03d9e7954c3d9208ae0eaa0835f8b0c2f;p=bdk-cli fix(create_tx,bump_fee): return errors instead of panicking `create_tx` and `bump_fee` called `.unwrap()` on `Result`s carrying user-supplied input, so a mistyped argument aborted the process with a Rust panic (exit 101) instead of a usage error (exit 1). - propagate `add_utxos` failures in `create_tx` and `bump_fee` with `?`, adding `BDKCliError::AddUtxoError` so the malformed outpoint is named - propagate `--add_data` base64 decoding and `PushBytesBuf` conversion failures in `create_tx` - stop flattening `add_utxos` errors into `CreateTxError::UnknownUtxo` in `create_sp_tx` and `create_dns_tx`, which discarded the real cause - add an integration test asserting exit 1, never 101, for an unknown outpoint and for malformed base64 Fixes #325 --- diff --git a/CHANGELOG.md b/CHANGELOG.md index ec50ea1..636e803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details. - Added support for Multipath (two-paths) descriptors. - Fixed the data directory and config.toml permission being world-readable (0755/0644) to 0700/0600 on Unix. +- Fixed `create_tx` and `bump_fee` panicking on malformed `--utxos` and `--add_data` values instead of returning an error ## [4.0.0] diff --git a/src/error.rs b/src/error.rs index bfad510..9ceb571 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,9 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum BDKCliError { + #[error("Add UTXO error: {0}")] + AddUtxoError(#[from] bdk_wallet::tx_builder::AddUtxoError), + #[error("Cannot provide both a multipath descriptor and a separate internal descriptor.")] AmbiguousDescriptors, diff --git a/src/handlers/dns/mod.rs b/src/handlers/dns/mod.rs index 628bfbd..6719db0 100644 --- a/src/handlers/dns/mod.rs +++ b/src/handlers/dns/mod.rs @@ -135,17 +135,13 @@ impl AsyncAppCommand>> for CreateDnsTxCommand { tx_builder.fee_rate(fee_rate); } if let Some(utxos) = &self.utxos { - tx_builder - .add_utxos(&utxos[..]) - .map_err(|_| bdk_wallet::error::CreateTxError::UnknownUtxo)?; + tx_builder.add_utxos(&utxos[..])?; } if let Some(unspendable) = &self.unspendable { tx_builder.unspendable(unspendable.to_vec()); } if let Some(base64_data) = &self.add_data { - let op_return_data = BASE64_STANDARD - .decode(base64_data) - .map_err(|e| Error::Generic(e.to_string()))?; + let op_return_data = BASE64_STANDARD.decode(base64_data)?; tx_builder.add_data( &PushBytesBuf::try_from(op_return_data) .map_err(|e| Error::Generic(e.to_string()))?, diff --git a/src/handlers/offline.rs b/src/handlers/offline.rs index 1395537..918a447 100644 --- a/src/handlers/offline.rs +++ b/src/handlers/offline.rs @@ -288,7 +288,7 @@ impl AppCommand>> for CreateTxCommand { } if let Some(utxos) = &self.utxos { - tx_builder.add_utxos(&utxos[..]).unwrap(); + tx_builder.add_utxos(&utxos[..])?; } if let Some(unspendable) = &self.unspendable { @@ -296,10 +296,14 @@ impl AppCommand>> for CreateTxCommand { } if let Some(base64_data) = &self.add_data { - let op_return_data = BASE64_STANDARD.decode(base64_data).unwrap(); - tx_builder.add_data(&PushBytesBuf::try_from(op_return_data).unwrap()); + let op_return_data = BASE64_STANDARD.decode(base64_data)?; + tx_builder.add_data( + &PushBytesBuf::try_from(op_return_data) + .map_err(|e| Error::Generic(e.to_string()))?, + ); } else if let Some(string_data) = &self.add_string { - let data = PushBytesBuf::try_from(string_data.as_bytes().to_vec()).unwrap(); + let data = PushBytesBuf::try_from(string_data.as_bytes().to_vec()) + .map_err(|e| Error::Generic(e.to_string()))?; tx_builder.add_data(&data); } @@ -319,8 +323,6 @@ impl AppCommand>> for CreateTxCommand { let psbt = tx_builder.finish()?; - // let psbt_base64 = BASE64_STANDARD.encode(psbt.serialize()); - Ok(PsbtResult::new(&psbt, Some(false))) } } @@ -443,9 +445,7 @@ impl AppCommand>> for CreateSpTxCommand { } if let Some(utxos) = &self.utxos { - tx_builder - .add_utxos(&utxos[..]) - .map_err(|_| bdk_wallet::error::CreateTxError::UnknownUtxo)?; + tx_builder.add_utxos(&utxos[..])?; } if let Some(unspendable) = &self.unspendable { @@ -453,9 +453,7 @@ impl AppCommand>> for CreateSpTxCommand { } if let Some(base64_data) = &self.add_data { - let op_return_data = BASE64_STANDARD - .decode(base64_data) - .map_err(|e| Error::Generic(e.to_string()))?; + let op_return_data = BASE64_STANDARD.decode(base64_data)?; tx_builder.add_data( &PushBytesBuf::try_from(op_return_data) .map_err(|e| Error::Generic(e.to_string()))?, @@ -599,7 +597,7 @@ impl AppCommand>> for BumpFeeCommand { } if let Some(utxos) = &self.utxos { - tx_builder.add_utxos(&utxos[..]).unwrap(); + tx_builder.add_utxos(&utxos[..])?; } if let Some(unspendable) = &self.unspendable { diff --git a/tests/integration/offline.rs b/tests/integration/offline.rs index 27b6bd1..3bb0f89 100644 --- a/tests/integration/offline.rs +++ b/tests/integration/offline.rs @@ -7,6 +7,8 @@ mod test_offline { use tempfile::TempDir; static WALLET_NAME: &str = "test_config_wallet"; + /// A `--to` argument, the wallet is unfunded, so nothing is spent. + static RECIPIENT: &str = "tb1p4tp4l6glyr2gs94neqcpr5gha7344nfyznfkc8szkreflscsdkgqsdent4:10000"; /// Helper to spin up a sandboxed CLI with the generated descriptors fn setup_wallet_config() -> (BdkCli, Command) { @@ -151,6 +153,42 @@ mod test_offline { .stderr(predicate::str::contains("Invalid")); } + /// A malformed `create_tx` argument must be reported as an error (exit 1), + /// never as a panic (exit 101). + #[test] + fn test_create_tx_rejects_malformed_input_without_panicking() { + let (cli, mut cmd_init) = setup_wallet_config(); + cmd_init.assert().success(); + + // Unknown outpoint + cli.wallet_cmd(&[ + "--wallet", + WALLET_NAME, + "create_tx", + "--to", + RECIPIENT, + "--utxos", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0", + ]) + .assert() + .code(1) + .stderr(predicate::str::contains("UTXO not found")); + + // Malformed base64 + cli.wallet_cmd(&[ + "--wallet", + WALLET_NAME, + "create_tx", + "--to", + RECIPIENT, + "--add_data", + "!!!not-base64!!!", + ]) + .assert() + .code(1) + .stderr(predicate::str::contains("Base64 decoding error")); + } + #[cfg(feature = "message_signer")] #[test] fn test_sign_message_and_verify_message() {