From a15ce35646f202a76b000e14eaff3286d578fdf0 Mon Sep 17 00:00:00 2001 From: Trevor Arjeski Date: Wed, 13 May 2026 19:47:35 +0000 Subject: [PATCH] refactor: return `DerivationResult` from `derive_at_index` This allows a caller of `derive_at_index` to fall back on the original descriptor (as a `Descriptor`) without having to `match` or `if-let` on the `Result` when there is no wildcard. We introduce a new result type called `DerivationResult` to handle this, but since the `Try` trait is not stabilized in our MSRV we cannot have `?` and must use `into_result()?` instead. --- src/descriptor/mod.rs | 70 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 5dc6e7592..f4688e806 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -650,6 +650,50 @@ impl ForEachKey for Descriptor { } } +/// Result of [`Descriptor::derive_at_index`]. +/// +/// When the source descriptor has no wildcard, +/// [`or_fallback`](DerivationResult::or_fallback) recovers the definite +/// descriptor instead of returning an error. +#[derive(Debug)] +#[must_use] +pub enum DerivationResult { + /// Derivation succeeded. + Ok(Descriptor), + /// The descriptor has no wildcard. Carries the original for fallback. + WithoutWildcard(Descriptor), + /// An error other than `NonDefiniteKeyError::NoWildcard`. + Error(NonDefiniteKeyError), +} + +impl DerivationResult { + /// Converts into a `Result`. + /// + /// Use [`or_fallback`](DerivationResult::or_fallback) if you want + /// `NonDefiniteKeyError::NoWildcard` errors to be recovered automatically. + pub fn into_result(self) -> Result, NonDefiniteKeyError> { + match self { + DerivationResult::Ok(d) => Ok(d), + DerivationResult::WithoutWildcard(_) => Err(NonDefiniteKeyError::NoWildcard), + DerivationResult::Error(e) => Err(e), + } + } + + /// Converts into a `Result`, recovering from `NonDefiniteKeyError::NoWildcard` by calling + /// [`into_definite`](Descriptor::into_definite) on the original descriptor. + pub fn or_fallback(self) -> Result, NonDefiniteKeyError> { + match self { + DerivationResult::Ok(d) => Ok(d), + DerivationResult::WithoutWildcard(original) => original.into_definite(), + DerivationResult::Error(e) => Err(e), + } + } + + /// Unwraps the result, panicking on non-recoverable errors. Ignores + /// `NonDefiniteKeyError::NoWildcard` errors by calling [`or_fallback`](DerivationResult::or_fallback). + pub fn unwrap(self) -> Descriptor { self.or_fallback().unwrap() } +} + impl Descriptor { /// Whether or not the descriptor has any wildcards i.e. `/*`. pub fn has_wildcard(&self) -> bool { self.for_any_key(|key| key.has_wildcard()) } @@ -703,15 +747,15 @@ impl Descriptor { /// - If the descriptor contains no wildcards /// - If index >= 2^31 /// - If the descriptor contains multi-path derivations - pub fn derive_at_index( - &self, - index: u32, - ) -> Result, NonDefiniteKeyError> { + #[allow(deprecated)] + pub fn derive_at_index(&self, index: u32) -> DerivationResult { if !self.has_wildcard() { - return Err(NonDefiniteKeyError::NoWildcard); + return DerivationResult::WithoutWildcard(self.clone()); + } + match self.at_derivation_index(index) { + Ok(d) => DerivationResult::Ok(d), + Err(e) => DerivationResult::Error(e), } - #[allow(deprecated)] - self.at_derivation_index(index) } /// Replaces all wildcards (i.e. `/*`) in the descriptor with a particular derivation index, @@ -926,7 +970,10 @@ impl Descriptor { } for i in range { - let concrete = self.derive_at_index(i)?.derived_descriptor(secp); + let concrete = self + .derive_at_index(i) + .into_result()? + .derived_descriptor(secp); if &concrete.script_pubkey() == script_pubkey { return Ok(Some((i, concrete))); } @@ -2827,7 +2874,10 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; "wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1/2)" ).unwrap(); assert!(!desc.has_wildcard()); - assert!(matches!(desc.derive_at_index(0), Err(NonDefiniteKeyError::NoWildcard))); + assert!(matches!( + desc.derive_at_index(0).into_result(), + Err(NonDefiniteKeyError::NoWildcard) + )); } #[test] @@ -2838,7 +2888,7 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; "wsh(multi(1,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/2))" ).unwrap(); assert!(desc.has_wildcard()); - assert!(desc.derive_at_index(0).is_ok()); + assert!(matches!(desc.derive_at_index(0), DerivationResult::Ok(_))); } #[test]