Skip to content

Commit 4ae5079

Browse files
committed
expression: replace methods for parsing numbers and locktimes
This eliminates another instance of `errstr` and provides well-typed errors when parsing locktimes and threshold values. It copies the AbsoluteLockTime and RelativeLockTime error variants from the main Error enum into ParseError. The old copies are now used only in script decoding, and will be removed in a later PR when we give script decoding its own error.
1 parent ee15056 commit 4ae5079

File tree

8 files changed

+116
-67
lines changed

8 files changed

+116
-67
lines changed

src/error.rs

+19
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,25 @@ use core::fmt;
88
use std::error;
99

1010
use crate::blanket_traits::StaticDebugAndDisplay;
11+
use crate::primitives::absolute_locktime::AbsLockTimeError;
12+
use crate::primitives::relative_locktime::RelLockTimeError;
1113
use crate::Box;
14+
1215
/// An error parsing a Miniscript object (policy, descriptor or miniscript)
1316
/// from a string.
1417
#[derive(Debug)]
1518
pub enum ParseError {
19+
/// Invalid absolute locktime
20+
AbsoluteLockTime(AbsLockTimeError),
21+
/// Invalid absolute locktime
22+
RelativeLockTime(RelLockTimeError),
1623
/// Failed to parse a public key or hash.
1724
///
1825
/// Note that the error information is lost for nostd compatibility reasons. See
1926
/// <https://users.rust-lang.org/t/how-to-box-an-error-type-retaining-std-error-only-when-std-is-enabled/>.
2027
FromStr(Box<dyn StaticDebugAndDisplay>),
28+
/// Failed to parse a number.
29+
Num(crate::ParseNumError),
2130
/// Error parsing a string into an expression tree.
2231
Tree(crate::ParseTreeError),
2332
}
@@ -29,14 +38,21 @@ impl ParseError {
2938
}
3039
}
3140

41+
impl From<crate::ParseNumError> for ParseError {
42+
fn from(e: crate::ParseNumError) -> Self { Self::Num(e) }
43+
}
44+
3245
impl From<crate::ParseTreeError> for ParseError {
3346
fn from(e: crate::ParseTreeError) -> Self { Self::Tree(e) }
3447
}
3548

3649
impl fmt::Display for ParseError {
3750
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3851
match self {
52+
ParseError::AbsoluteLockTime(ref e) => e.fmt(f),
53+
ParseError::RelativeLockTime(ref e) => e.fmt(f),
3954
ParseError::FromStr(ref e) => e.fmt(f),
55+
ParseError::Num(ref e) => e.fmt(f),
4056
ParseError::Tree(ref e) => e.fmt(f),
4157
}
4258
}
@@ -46,7 +62,10 @@ impl fmt::Display for ParseError {
4662
impl error::Error for ParseError {
4763
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
4864
match self {
65+
ParseError::AbsoluteLockTime(ref e) => Some(e),
66+
ParseError::RelativeLockTime(ref e) => Some(e),
4967
ParseError::FromStr(..) => None,
68+
ParseError::Num(ref e) => Some(e),
5069
ParseError::Tree(ref e) => Some(e),
5170
}
5271
}

src/expression/error.rs

+34-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
//! Expression-related errors
44
5-
use core::fmt;
5+
use core::{fmt, num};
66

77
use crate::descriptor::checksum;
88
use crate::prelude::*;
@@ -168,6 +168,36 @@ impl std::error::Error for ParseTreeError {
168168
}
169169
}
170170

171+
/// Error parsing a number.
172+
#[derive(Clone, Debug, PartialEq, Eq)]
173+
pub enum ParseNumError {
174+
/// Failed to parse the number at all.
175+
StdParse(num::ParseIntError),
176+
/// Number had a leading zero, + or -.
177+
InvalidLeadingDigit(char),
178+
}
179+
180+
impl fmt::Display for ParseNumError {
181+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182+
match self {
183+
ParseNumError::StdParse(ref e) => e.fmt(f),
184+
ParseNumError::InvalidLeadingDigit(ch) => {
185+
write!(f, "numbers must start with 1-9, not {}", ch)
186+
}
187+
}
188+
}
189+
}
190+
191+
#[cfg(feature = "std")]
192+
impl std::error::Error for ParseNumError {
193+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
194+
match self {
195+
ParseNumError::StdParse(ref e) => Some(e),
196+
ParseNumError::InvalidLeadingDigit(..) => None,
197+
}
198+
}
199+
}
200+
171201
/// Error parsing a threshold expression.
172202
#[derive(Clone, Debug, PartialEq, Eq)]
173203
pub enum ParseThresholdError {
@@ -176,9 +206,7 @@ pub enum ParseThresholdError {
176206
/// The threshold value appeared to be a sub-expression rather than a number.
177207
KNotTerminal,
178208
/// Failed to parse the threshold value.
179-
// FIXME this should be a more specific type. Will be handled in a later PR
180-
// that rewrites the expression parsing logic.
181-
ParseK(String),
209+
ParseK(ParseNumError),
182210
/// Threshold parameters were invalid.
183211
Threshold(ThresholdError),
184212
}
@@ -190,7 +218,7 @@ impl fmt::Display for ParseThresholdError {
190218
match *self {
191219
NoChildren => f.write_str("expected threshold, found terminal"),
192220
KNotTerminal => f.write_str("expected positive integer, found expression"),
193-
ParseK(ref x) => write!(f, "failed to parse threshold value {}", x),
221+
ParseK(ref x) => write!(f, "failed to parse threshold value: {}", x),
194222
Threshold(ref e) => e.fmt(f),
195223
}
196224
}
@@ -204,7 +232,7 @@ impl std::error::Error for ParseThresholdError {
204232
match *self {
205233
NoChildren => None,
206234
KNotTerminal => None,
207-
ParseK(..) => None,
235+
ParseK(ref e) => Some(e),
208236
Threshold(ref e) => Some(e),
209237
}
210238
}

src/expression/mod.rs

+44-23
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
66
mod error;
77

8+
use core::ops;
89
use core::str::FromStr;
9-
use core::{fmt, ops};
1010

11-
pub use self::error::{ParseThresholdError, ParseTreeError};
11+
pub use self::error::{ParseNumError, ParseThresholdError, ParseTreeError};
1212
use crate::blanket_traits::StaticDebugAndDisplay;
1313
use crate::descriptor::checksum::verify_checksum;
1414
use crate::iter::{self, TreeLike};
1515
use crate::prelude::*;
16-
use crate::{errstr, Error, ParseError, Threshold, MAX_RECURSION_DEPTH};
16+
use crate::{AbsLockTime, Error, ParseError, RelLockTime, Threshold, MAX_RECURSION_DEPTH};
1717

1818
/// Allowed characters are descriptor strings.
1919
pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
@@ -138,6 +138,38 @@ impl<'a> Tree<'a> {
138138
}
139139
}
140140

141+
/// Check that a tree node has a single terminal child which is an absolute locktime.
142+
///
143+
/// Returns an error assuming that the node is named "after".
144+
///
145+
/// If so, parse the locktime from a string and return it.
146+
pub fn verify_after(&self) -> Result<AbsLockTime, ParseError> {
147+
self.verify_n_children("after", 1..=1)
148+
.map_err(ParseError::Tree)?;
149+
self.args[0]
150+
.verify_n_children("absolute locktime", 0..=0)
151+
.map_err(ParseError::Tree)?;
152+
parse_num(self.args[0].name)
153+
.map_err(ParseError::Num)
154+
.and_then(|n| AbsLockTime::from_consensus(n).map_err(ParseError::AbsoluteLockTime))
155+
}
156+
157+
/// Check that a tree node has a single terminal child which is a relative locktime.
158+
///
159+
/// Returns an error assuming that the node is named "older".
160+
///
161+
/// If so, parse the locktime from a string and return it.
162+
pub fn verify_older(&self) -> Result<RelLockTime, ParseError> {
163+
self.verify_n_children("older", 1..=1)
164+
.map_err(ParseError::Tree)?;
165+
self.args[0]
166+
.verify_n_children("relative locktime", 0..=0)
167+
.map_err(ParseError::Tree)?;
168+
parse_num(self.args[0].name)
169+
.map_err(ParseError::Num)
170+
.and_then(|n| RelLockTime::from_consensus(n).map_err(ParseError::RelativeLockTime))
171+
}
172+
141173
/// Check that a tree node is a terminal (has no children).
142174
///
143175
/// If so, parse the terminal from a string and return it.
@@ -377,34 +409,23 @@ impl<'a> Tree<'a> {
377409
return Err(ParseThresholdError::KNotTerminal);
378410
}
379411

380-
let k = parse_num(self.args[0].name)
381-
.map_err(|e| ParseThresholdError::ParseK(e.to_string()))? as usize;
412+
let k = parse_num(self.args[0].name).map_err(ParseThresholdError::ParseK)? as usize;
382413
Threshold::new(k, vec![(); self.args.len() - 1]).map_err(ParseThresholdError::Threshold)
383414
}
384415
}
385416

386417
/// Parse a string as a u32, for timelocks or thresholds
387-
pub fn parse_num(s: &str) -> Result<u32, Error> {
388-
if s.len() > 1 {
389-
let ch = s.chars().next().unwrap();
418+
pub fn parse_num(s: &str) -> Result<u32, ParseNumError> {
419+
if s == "0" {
420+
// Special-case 0 since it is the only number which may start with a leading zero.
421+
return Ok(0);
422+
}
423+
if let Some(ch) = s.chars().next() {
390424
if !('1'..='9').contains(&ch) {
391-
return Err(Error::Unexpected("Number must start with a digit 1-9".to_string()));
425+
return Err(ParseNumError::InvalidLeadingDigit(ch));
392426
}
393427
}
394-
u32::from_str(s).map_err(|_| errstr(s))
395-
}
396-
397-
/// Attempts to parse a terminal expression
398-
pub fn terminal<T, F, Err>(term: &Tree, convert: F) -> Result<T, Error>
399-
where
400-
F: FnOnce(&str) -> Result<T, Err>,
401-
Err: fmt::Display,
402-
{
403-
if term.args.is_empty() {
404-
convert(term.name).map_err(|e| Error::Unexpected(e.to_string()))
405-
} else {
406-
Err(errstr(term.name))
407-
}
428+
u32::from_str(s).map_err(ParseNumError::StdParse)
408429
}
409430

410431
#[cfg(test)]

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ use bitcoin::{script, Opcode};
137137
pub use crate::blanket_traits::FromStrKey;
138138
pub use crate::descriptor::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey};
139139
pub use crate::error::ParseError;
140-
pub use crate::expression::{ParseThresholdError, ParseTreeError};
140+
pub use crate::expression::{ParseNumError, ParseThresholdError, ParseTreeError};
141141
pub use crate::interpreter::Interpreter;
142142
pub use crate::miniscript::analyzable::{AnalysisError, ExtParams};
143143
pub use crate::miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, SigType, Tap};

src/miniscript/astelem.rs

+9-14
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ use crate::miniscript::context::SigType;
1515
use crate::miniscript::ScriptContext;
1616
use crate::prelude::*;
1717
use crate::util::MsKeyBuilder;
18-
use crate::{
19-
expression, AbsLockTime, Error, FromStrKey, Miniscript, MiniscriptKey, RelLockTime, Terminal,
20-
ToPublicKey,
21-
};
18+
use crate::{expression, Error, FromStrKey, Miniscript, MiniscriptKey, Terminal, ToPublicKey};
2219

2320
impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Arc<Terminal<Pk, Ctx>> {
2421
fn from_tree(top: &expression::Tree) -> Result<Arc<Terminal<Pk, Ctx>>, Error> {
@@ -54,16 +51,14 @@ impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Termina
5451
.verify_terminal_parent("pk_h", "public key")
5552
.map(Terminal::PkH)
5653
.map_err(Error::Parse),
57-
"after" => expression::terminal(&top.args[0], |x| {
58-
expression::parse_num(x)
59-
.and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime))
60-
.map(Terminal::After)
61-
}),
62-
"older" => expression::terminal(&top.args[0], |x| {
63-
expression::parse_num(x)
64-
.and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime))
65-
.map(Terminal::Older)
66-
}),
54+
"after" => top
55+
.verify_after()
56+
.map_err(Error::Parse)
57+
.map(Terminal::After),
58+
"older" => top
59+
.verify_older()
60+
.map_err(Error::Parse)
61+
.map(Terminal::Older),
6762
"sha256" => top
6863
.verify_terminal_parent("sha256", "hash")
6964
.map(Terminal::Sha256)

src/policy/concrete.rs

+5-11
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,9 @@ impl<Pk: FromStrKey> Policy<Pk> {
867867
if !allow_prob {
868868
return Err(Error::AtOutsideOr(top.name.to_owned()));
869869
}
870-
frag_prob = expression::parse_num(prob)? as usize;
870+
frag_prob = expression::parse_num(prob)
871+
.map_err(crate::ParseError::Num)
872+
.map_err(Error::Parse)? as usize;
871873
frag_name = name;
872874
}
873875
(Some(_), Some(_), Some(_)) => {
@@ -891,16 +893,8 @@ impl<Pk: FromStrKey> Policy<Pk> {
891893
.verify_terminal_parent("pk", "public key")
892894
.map(Policy::Key)
893895
.map_err(Error::Parse),
894-
"after" => expression::terminal(&top.args[0], |x| {
895-
expression::parse_num(x)
896-
.and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime))
897-
.map(Policy::After)
898-
}),
899-
"older" => expression::terminal(&top.args[0], |x| {
900-
expression::parse_num(x)
901-
.and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime))
902-
.map(Policy::Older)
903-
}),
896+
"after" => top.verify_after().map_err(Error::Parse).map(Policy::After),
897+
"older" => top.verify_older().map_err(Error::Parse).map(Policy::Older),
904898
"sha256" => top
905899
.verify_terminal_parent("sha256", "hash")
906900
.map(Policy::Sha256)

src/policy/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -320,14 +320,14 @@ mod tests {
320320
ConcretePol::from_str("thresh(3,after(0),pk(),pk())")
321321
.unwrap_err()
322322
.to_string(),
323-
"unexpected «absolute locktimes in Miniscript have a minimum value of 1»",
323+
"absolute locktimes in Miniscript have a minimum value of 1",
324324
);
325325

326326
assert_eq!(
327327
ConcretePol::from_str("thresh(2,older(2147483650),pk(),pk())")
328328
.unwrap_err()
329329
.to_string(),
330-
"unexpected «locktime value 2147483650 is not a valid BIP68 relative locktime»"
330+
"locktime value 2147483650 is not a valid BIP68 relative locktime"
331331
);
332332
}
333333

src/policy/semantic.rs

+2-10
Original file line numberDiff line numberDiff line change
@@ -302,16 +302,8 @@ impl<Pk: FromStrKey> expression::FromTree for Policy<Pk> {
302302
.verify_terminal_parent("pk", "public key")
303303
.map(Policy::Key)
304304
.map_err(Error::Parse),
305-
"after" => expression::terminal(&top.args[0], |x| {
306-
expression::parse_num(x)
307-
.and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime))
308-
.map(Policy::After)
309-
}),
310-
"older" => expression::terminal(&top.args[0], |x| {
311-
expression::parse_num(x)
312-
.and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime))
313-
.map(Policy::Older)
314-
}),
305+
"after" => top.verify_after().map_err(Error::Parse).map(Policy::After),
306+
"older" => top.verify_older().map_err(Error::Parse).map(Policy::Older),
315307
"sha256" => top
316308
.verify_terminal_parent("sha256", "hash")
317309
.map(Policy::Sha256)

0 commit comments

Comments
 (0)