Skip to content

Commit bd68e35

Browse files
committed
Add manual Ord impls for concrete and semantic Policy.
Replace derive(Ord) with variant-name ordering and same-kind locktime comparison by consensus u32, and add regression tests.
1 parent 99501a5 commit bd68e35

3 files changed

Lines changed: 156 additions & 4 deletions

File tree

src/policy/concrete.rs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! Concrete Policies
44
//!
55
6-
use core::{fmt, str};
6+
use core::{cmp, fmt, str};
77
#[cfg(feature = "std")]
88
use std::error;
99

@@ -24,6 +24,7 @@ use crate::expression::{self, FromTree};
2424
use crate::iter::{Tree, TreeLike};
2525
use crate::miniscript::types::extra_props::TimelockInfo;
2626
use crate::prelude::*;
27+
use crate::primitives::locktime_consensus_cmp;
2728
use crate::sync::Arc;
2829
#[cfg(all(doc, not(feature = "compiler")))]
2930
use crate::Descriptor;
@@ -41,7 +42,7 @@ const MAX_COMPILATION_LEAVES: usize = 1024;
4142
// Currently the vectors in And/Or are limited to two elements, this is a general miniscript thing
4243
// not specific to rust-miniscript. Eventually we would like to extend these to be n-ary, but first
4344
// we need to decide on a game plan for how to efficiently compile n-ary disjunctions
44-
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45+
#[derive(Clone, PartialEq, Eq, Hash)]
4546
pub enum Policy<Pk: MiniscriptKey> {
4647
/// Unsatisfiable.
4748
Unsatisfiable,
@@ -70,6 +71,57 @@ pub enum Policy<Pk: MiniscriptKey> {
7071
Thresh(Threshold<Arc<Self>, 0>),
7172
}
7273

74+
impl<Pk: MiniscriptKey> Policy<Pk> {
75+
fn variant_name(&self) -> &'static str {
76+
match *self {
77+
Self::Unsatisfiable => "unsatisfiable",
78+
Self::Trivial => "trivial",
79+
Self::Key(_) => "key",
80+
Self::After(_) => "after",
81+
Self::Older(_) => "older",
82+
Self::Sha256(_) => "sha256",
83+
Self::Hash256(_) => "hash256",
84+
Self::Ripemd160(_) => "ripemd160",
85+
Self::Hash160(_) => "hash160",
86+
Self::And(_) => "and",
87+
Self::Or(_) => "or",
88+
Self::Thresh(_) => "thresh",
89+
}
90+
}
91+
}
92+
93+
impl<Pk: MiniscriptKey> PartialOrd for Policy<Pk> {
94+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) }
95+
}
96+
97+
impl<Pk: MiniscriptKey> Ord for Policy<Pk> {
98+
fn cmp(&self, other: &Self) -> cmp::Ordering {
99+
match self.variant_name().cmp(other.variant_name()) {
100+
cmp::Ordering::Equal => {}
101+
ord => return ord,
102+
}
103+
match (self, other) {
104+
(Self::Unsatisfiable, Self::Unsatisfiable) => cmp::Ordering::Equal,
105+
(Self::Trivial, Self::Trivial) => cmp::Ordering::Equal,
106+
(Self::Key(a), Self::Key(b)) => a.cmp(b),
107+
(Self::After(a), Self::After(b)) => {
108+
locktime_consensus_cmp(a.to_consensus_u32(), b.to_consensus_u32())
109+
}
110+
(Self::Older(a), Self::Older(b)) => {
111+
locktime_consensus_cmp(a.to_consensus_u32(), b.to_consensus_u32())
112+
}
113+
(Self::Sha256(a), Self::Sha256(b)) => a.cmp(b),
114+
(Self::Hash256(a), Self::Hash256(b)) => a.cmp(b),
115+
(Self::Ripemd160(a), Self::Ripemd160(b)) => a.cmp(b),
116+
(Self::Hash160(a), Self::Hash160(b)) => a.cmp(b),
117+
(Self::And(a), Self::And(b)) => a.cmp(b),
118+
(Self::Or(a), Self::Or(b)) => a.cmp(b),
119+
(Self::Thresh(a), Self::Thresh(b)) => a.cmp(b),
120+
_ => unreachable!("variant_name ensures same variant"),
121+
}
122+
}
123+
}
124+
73125
/// Detailed error type for concrete policies.
74126
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
75127
pub enum PolicyError {
@@ -1389,4 +1441,26 @@ mod tests {
13891441
.parse::<Policy<String>>()
13901442
.unwrap_err();
13911443
}
1444+
1445+
#[test]
1446+
fn policy_ord_is_consistent() {
1447+
// Same direction as numeric — passes under both old and new scheme.
1448+
let a = Policy::<String>::from_str("after(100)").unwrap();
1449+
let b = Policy::<String>::from_str("after(200)").unwrap();
1450+
assert!(a < b, "after(100) should be less than after(200)");
1451+
1452+
// Cross-variant: must not be equal.
1453+
let c = Policy::<String>::from_str("older(100)").unwrap();
1454+
assert!(a != c, "after and older variants must not compare equal");
1455+
1456+
// Numeric consensus ordering: 9 < 10.
1457+
let d = Policy::<String>::from_str("after(9)").unwrap();
1458+
let e = Policy::<String>::from_str("after(10)").unwrap();
1459+
assert!(d < e, "after(9) < after(10) under consensus u32 ordering");
1460+
1461+
// "trivial" < "unsatisfiable" alphabetically.
1462+
let trivial = Policy::<String>::from_str("TRIVIAL").unwrap();
1463+
let unsat = Policy::<String>::from_str("UNSATISFIABLE").unwrap();
1464+
assert!(trivial < unsat, "trivial < unsatisfiable under variant_name ordering");
1465+
}
13921466
}

src/policy/semantic.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
//! We use the terms "semantic" and "abstract" interchangeably because
66
//! "abstract" is a reserved keyword in Rust.
77
8-
use core::{fmt, str};
8+
use core::{cmp, fmt, str};
99

1010
use bitcoin::{absolute, relative};
1111

1212
use super::ENTAILMENT_MAX_TERMINALS;
1313
use crate::iter::{Tree, TreeLike};
1414
use crate::prelude::*;
15+
use crate::primitives::locktime_consensus_cmp;
1516
use crate::sync::Arc;
1617
use crate::{
1718
expression, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, Threshold,
@@ -24,7 +25,7 @@ use crate::{
2425
/// Semantic policies store only hashes of keys to ensure that objects
2526
/// representing the same policy are lifted to the same abstract `Policy`,
2627
/// regardless of their choice of `pk` or `pk_h` nodes.
27-
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
28+
#[derive(Clone, PartialEq, Eq)]
2829
pub enum Policy<Pk: MiniscriptKey> {
2930
/// Unsatisfiable.
3031
Unsatisfiable,
@@ -48,6 +49,53 @@ pub enum Policy<Pk: MiniscriptKey> {
4849
Thresh(Threshold<Arc<Self>, 0>),
4950
}
5051

52+
impl<Pk: MiniscriptKey> Policy<Pk> {
53+
fn variant_name(&self) -> &'static str {
54+
match *self {
55+
Self::Unsatisfiable => "unsatisfiable",
56+
Self::Trivial => "trivial",
57+
Self::Key(_) => "key",
58+
Self::After(_) => "after",
59+
Self::Older(_) => "older",
60+
Self::Sha256(_) => "sha256",
61+
Self::Hash256(_) => "hash256",
62+
Self::Ripemd160(_) => "ripemd160",
63+
Self::Hash160(_) => "hash160",
64+
Self::Thresh(_) => "thresh",
65+
}
66+
}
67+
}
68+
69+
impl<Pk: MiniscriptKey> PartialOrd for Policy<Pk> {
70+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) }
71+
}
72+
73+
impl<Pk: MiniscriptKey> Ord for Policy<Pk> {
74+
fn cmp(&self, other: &Self) -> cmp::Ordering {
75+
match self.variant_name().cmp(other.variant_name()) {
76+
cmp::Ordering::Equal => {}
77+
ord => return ord,
78+
}
79+
match (self, other) {
80+
(Self::Unsatisfiable, Self::Unsatisfiable) => cmp::Ordering::Equal,
81+
(Self::Trivial, Self::Trivial) => cmp::Ordering::Equal,
82+
(Self::Key(a), Self::Key(b)) => a.cmp(b),
83+
(Self::After(a), Self::After(b)) => {
84+
locktime_consensus_cmp(a.to_consensus_u32(), b.to_consensus_u32())
85+
}
86+
(Self::Older(a), Self::Older(b)) => {
87+
locktime_consensus_cmp(a.to_consensus_u32(), b.to_consensus_u32())
88+
}
89+
(Self::Sha256(a), Self::Sha256(b)) => a.cmp(b),
90+
(Self::Hash256(a), Self::Hash256(b)) => a.cmp(b),
91+
(Self::Ripemd160(a), Self::Ripemd160(b)) => a.cmp(b),
92+
(Self::Hash160(a), Self::Hash160(b)) => a.cmp(b),
93+
(Self::Thresh(a), Self::Thresh(b)) => a.cmp(b),
94+
_ => unreachable!("variant_name ensures same variant"),
95+
}
96+
}
97+
}
98+
5199
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Policy<Pk> {
52100
fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool {
53101
self.pre_order_iter().all(|policy| match policy {
@@ -687,6 +735,28 @@ mod tests {
687735

688736
type StringPolicy = Policy<String>;
689737

738+
#[test]
739+
fn policy_ord_is_consistent() {
740+
// Same direction as numeric — passes under both old and new scheme.
741+
let a = StringPolicy::from_str("after(100)").unwrap();
742+
let b = StringPolicy::from_str("after(200)").unwrap();
743+
assert!(a < b, "after(100) should be less than after(200)");
744+
745+
// Cross-variant: must not be equal.
746+
let c = StringPolicy::from_str("older(100)").unwrap();
747+
assert!(a != c, "after and older variants must not compare equal");
748+
749+
// Numeric consensus ordering: 9 < 10.
750+
let d = StringPolicy::from_str("after(9)").unwrap();
751+
let e = StringPolicy::from_str("after(10)").unwrap();
752+
assert!(d < e, "after(9) < after(10) under consensus u32 ordering");
753+
754+
// "trivial" < "unsatisfiable" alphabetically.
755+
let trivial = StringPolicy::from_str("TRIVIAL").unwrap();
756+
let unsat = StringPolicy::from_str("UNSATISFIABLE").unwrap();
757+
assert!(trivial < unsat, "trivial < unsatisfiable under variant_name ordering");
758+
}
759+
690760
#[test]
691761
fn parse_policy_err() {
692762
assert!(StringPolicy::from_str("(").is_err());

src/primitives/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,11 @@
1414
pub mod absolute_locktime;
1515
pub mod relative_locktime;
1616
pub mod threshold;
17+
18+
use core::cmp;
19+
20+
/// Compares two same-kind locktimes by consensus `u32`.
21+
///
22+
/// Used for locktime ordering in `Policy::Ord`, `Terminal::cmp`, and (via the latter)
23+
/// `Miniscript::Ord`.
24+
pub(crate) fn locktime_consensus_cmp(a: u32, b: u32) -> cmp::Ordering { a.cmp(&b) }

0 commit comments

Comments
 (0)