Skip to content

Commit

Permalink
Merge #594: Implement TreeLike for policy::Concrete
Browse files Browse the repository at this point in the history
1efc7a3 Refactor check_timelocks helper (Tobin C. Harding)
4749931 Use TreeLike to implement check_timelocks_helper (Tobin C. Harding)
1cbc018 Add unit test for check_timelocks API (Tobin C. Harding)
64488d6 Use TreeLike to implement translate_pk (Tobin C. Harding)
3282193 Add basic unit test for translate_pk (Tobin C. Harding)
c81389f Use TreeLike to implement for_each_key (Tobin C. Harding)
b3c0955 Add unit test coverage for_each_key (Tobin C. Harding)
9280db2 Use TreeLike to implement num_tap_leaves (Tobin C. Harding)
3fc604a Add basic unit test for num_tap_leaves (Tobin C. Harding)
b7f8939 Use TreeLike to implement keys (Tobin C. Harding)
984576c Add basic unit test for keys (Tobin C. Harding)
2b11f64 Use TreeLike to implement translate_unsatisfiable_pk (Tobin C. Harding)
4d22908 Add basic unit test for translate_unsatisfiable_pk (Tobin C. Harding)
578dc69 Remove code comment from soon-to-be duplicated pattern (Tobin C. Harding)
277b587 Implement TreeLike for policy::Concrete (Tobin C. Harding)
82c0eee Remove PolicyArc (Tobin C. Harding)

Pull request description:

  Make a start on removing recursion in the `policy::concrete` module.

  - Patch 1: Remove`PolicyArc`
  - Patch2: Implement `TreeLike` for `concrete::Policy` (does not use it)
  - Patch 3: Trivial code comment deletion
  - Subsequent patches are pairs
     - Add a basic unit test for the function we are about to patch
     - Remove recursion from a function (the one we just tested)
  - Final patch is a refactoring

  There is still some to go but this is already a pretty heavy review burden.

ACKs for top commit:
  apoelstra:
    ACK 1efc7a3
  sanket1729:
    ACK 1efc7a3

Tree-SHA512: 529fcf60adda1ff43a95ee896dccac746870b527fec5a638e2377206220aa87369132d037a44a048ca25fc67df1b6309d53d8fdfb7134299421396538ec043d4
  • Loading branch information
sanket1729 committed Sep 27, 2023
2 parents 3c6ae26 + 1efc7a3 commit c42b1cb
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 337 deletions.
28 changes: 27 additions & 1 deletion src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use tree::{
};

use crate::sync::Arc;
use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal};
use crate::{policy, Miniscript, MiniscriptKey, ScriptContext, Terminal};

impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript<Pk, Ctx> {
fn as_node(&self) -> Tree<Self> {
Expand Down Expand Up @@ -68,3 +68,29 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for Arc<Miniscript<Pk, Ctx>
}
}
}

impl<'a, Pk: MiniscriptKey> TreeLike for &'a policy::Concrete<Pk> {
fn as_node(&self) -> Tree<Self> {
use policy::Concrete::*;
match *self {
Unsatisfiable | Trivial | Key(_) | After(_) | Older(_) | Sha256(_) | Hash256(_)
| Ripemd160(_) | Hash160(_) => Tree::Nullary,
And(ref subs) => Tree::Nary(subs.iter().map(Arc::as_ref).collect()),
Or(ref v) => Tree::Nary(v.iter().map(|(_, p)| p.as_ref()).collect()),
Threshold(_, ref subs) => Tree::Nary(subs.iter().map(Arc::as_ref).collect()),
}
}
}

impl<'a, Pk: MiniscriptKey> TreeLike for Arc<policy::Concrete<Pk>> {
fn as_node(&self) -> Tree<Self> {
use policy::Concrete::*;
match self.as_ref() {
Unsatisfiable | Trivial | Key(_) | After(_) | Older(_) | Sha256(_) | Hash256(_)
| Ripemd160(_) | Hash160(_) => Tree::Nullary,
And(ref subs) => Tree::Nary(subs.iter().map(Arc::clone).collect()),
Or(ref v) => Tree::Nary(v.iter().map(|(_, p)| Arc::clone(p)).collect()),
Threshold(_, ref subs) => Tree::Nary(subs.iter().map(Arc::clone).collect()),
}
}
}
1 change: 0 additions & 1 deletion src/miniscript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
{
let mut translated = vec![];
for data in Arc::new(self.clone()).post_order_iter() {
// convenience method to reduce typing
let child_n = |n| Arc::clone(&translated[data.child_indices[n]]);

let new_term = match data.node.node {
Expand Down
140 changes: 92 additions & 48 deletions src/policy/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,10 +786,14 @@ where
}
Concrete::And(ref subs) => {
assert_eq!(subs.len(), 2, "and takes 2 args");
let mut left = best_compilations(policy_cache, &subs[0], sat_prob, dissat_prob)?;
let mut right = best_compilations(policy_cache, &subs[1], sat_prob, dissat_prob)?;
let mut q_zero_right = best_compilations(policy_cache, &subs[1], sat_prob, None)?;
let mut q_zero_left = best_compilations(policy_cache, &subs[0], sat_prob, None)?;
let mut left =
best_compilations(policy_cache, subs[0].as_ref(), sat_prob, dissat_prob)?;
let mut right =
best_compilations(policy_cache, subs[1].as_ref(), sat_prob, dissat_prob)?;
let mut q_zero_right =
best_compilations(policy_cache, subs[1].as_ref(), sat_prob, None)?;
let mut q_zero_left =
best_compilations(policy_cache, subs[0].as_ref(), sat_prob, None)?;

compile_binary!(&mut left, &mut right, [1.0, 1.0], Terminal::AndB);
compile_binary!(&mut right, &mut left, [1.0, 1.0], Terminal::AndB);
Expand All @@ -813,48 +817,56 @@ where
let rw = subs[1].0 as f64 / total;

//and-or
if let (Concrete::And(x), _) = (&subs[0].1, &subs[1].1) {
if let (Concrete::And(x), _) = (subs[0].1.as_ref(), subs[1].1.as_ref()) {
let mut a1 = best_compilations(
policy_cache,
&x[0],
x[0].as_ref(),
lw * sat_prob,
Some(dissat_prob.unwrap_or(0 as f64) + rw * sat_prob),
)?;
let mut a2 = best_compilations(policy_cache, &x[0], lw * sat_prob, None)?;
let mut a2 = best_compilations(policy_cache, x[0].as_ref(), lw * sat_prob, None)?;

let mut b1 = best_compilations(
policy_cache,
&x[1],
x[1].as_ref(),
lw * sat_prob,
Some(dissat_prob.unwrap_or(0 as f64) + rw * sat_prob),
)?;
let mut b2 = best_compilations(policy_cache, &x[1], lw * sat_prob, None)?;
let mut b2 = best_compilations(policy_cache, x[1].as_ref(), lw * sat_prob, None)?;

let mut c =
best_compilations(policy_cache, &subs[1].1, rw * sat_prob, dissat_prob)?;
let mut c = best_compilations(
policy_cache,
subs[1].1.as_ref(),
rw * sat_prob,
dissat_prob,
)?;

compile_tern!(&mut a1, &mut b2, &mut c, [lw, rw]);
compile_tern!(&mut b1, &mut a2, &mut c, [lw, rw]);
};
if let (_, Concrete::And(x)) = (&subs[0].1, &subs[1].1) {
if let (_, Concrete::And(x)) = (&subs[0].1.as_ref(), subs[1].1.as_ref()) {
let mut a1 = best_compilations(
policy_cache,
&x[0],
x[0].as_ref(),
rw * sat_prob,
Some(dissat_prob.unwrap_or(0 as f64) + lw * sat_prob),
)?;
let mut a2 = best_compilations(policy_cache, &x[0], rw * sat_prob, None)?;
let mut a2 = best_compilations(policy_cache, x[0].as_ref(), rw * sat_prob, None)?;

let mut b1 = best_compilations(
policy_cache,
&x[1],
x[1].as_ref(),
rw * sat_prob,
Some(dissat_prob.unwrap_or(0 as f64) + lw * sat_prob),
)?;
let mut b2 = best_compilations(policy_cache, &x[1], rw * sat_prob, None)?;
let mut b2 = best_compilations(policy_cache, x[1].as_ref(), rw * sat_prob, None)?;

let mut c =
best_compilations(policy_cache, &subs[0].1, lw * sat_prob, dissat_prob)?;
let mut c = best_compilations(
policy_cache,
subs[0].1.as_ref(),
lw * sat_prob,
dissat_prob,
)?;

compile_tern!(&mut a1, &mut b2, &mut c, [rw, lw]);
compile_tern!(&mut b1, &mut a2, &mut c, [rw, lw]);
Expand All @@ -873,12 +885,22 @@ where
let mut r_comp = vec![];

for dissat_prob in dissat_probs(rw).iter() {
let l = best_compilations(policy_cache, &subs[0].1, lw * sat_prob, *dissat_prob)?;
let l = best_compilations(
policy_cache,
subs[0].1.as_ref(),
lw * sat_prob,
*dissat_prob,
)?;
l_comp.push(l);
}

for dissat_prob in dissat_probs(lw).iter() {
let r = best_compilations(policy_cache, &subs[1].1, rw * sat_prob, *dissat_prob)?;
let r = best_compilations(
policy_cache,
subs[1].1.as_ref(),
rw * sat_prob,
*dissat_prob,
)?;
r_comp.push(r);
}

Expand Down Expand Up @@ -913,8 +935,8 @@ where
let sp = sat_prob * k_over_n;
//Expressions must be dissatisfiable
let dp = Some(dissat_prob.unwrap_or(0 as f64) + (1.0 - k_over_n) * sat_prob);
let be = best(types::Base::B, policy_cache, ast, sp, dp)?;
let bw = best(types::Base::W, policy_cache, ast, sp, dp)?;
let be = best(types::Base::B, policy_cache, ast.as_ref(), sp, dp)?;
let bw = best(types::Base::W, policy_cache, ast.as_ref(), sp, dp)?;

let diff = be.cost_1d(sp, dp) - bw.cost_1d(sp, dp);
best_es.push((be.comp_ext_data, be));
Expand Down Expand Up @@ -947,7 +969,7 @@ where
let key_vec: Vec<Pk> = subs
.iter()
.filter_map(|s| {
if let Concrete::Key(ref pk) = *s {
if let Concrete::Key(ref pk) = s.as_ref() {
Some(pk.clone())
} else {
None
Expand All @@ -967,9 +989,10 @@ where
_ if k == subs.len() => {
let mut it = subs.iter();
let mut policy = it.next().expect("No sub policy in thresh() ?").clone();
policy = it.fold(policy, |acc, pol| Concrete::And(vec![acc, pol.clone()]));
policy =
it.fold(policy, |acc, pol| Concrete::And(vec![acc, pol.clone()]).into());

ret = best_compilations(policy_cache, &policy, sat_prob, dissat_prob)?;
ret = best_compilations(policy_cache, policy.as_ref(), sat_prob, dissat_prob)?;
}
_ => {}
}
Expand Down Expand Up @@ -1178,8 +1201,11 @@ mod tests {
fn compile_timelocks() {
// artificially create a policy that is problematic and try to compile
let pol: SPolicy = Concrete::And(vec![
Concrete::Key("A".to_string()),
Concrete::And(vec![Concrete::after(9), Concrete::after(1000_000_000)]),
Arc::new(Concrete::Key("A".to_string())),
Arc::new(Concrete::And(vec![
Arc::new(Concrete::after(9)),
Arc::new(Concrete::after(1000_000_000)),
])),
]);
assert!(pol.compile::<Segwitv0>().is_err());

Expand Down Expand Up @@ -1273,13 +1299,22 @@ mod tests {

// Liquid policy
let policy: BPolicy = Concrete::Or(vec![
(127, Concrete::Threshold(3, key_pol[0..5].to_owned())),
(
127,
Arc::new(Concrete::Threshold(
3,
key_pol[0..5].iter().map(|p| (p.clone()).into()).collect(),
)),
),
(
1,
Concrete::And(vec![
Concrete::Older(Sequence::from_height(10000)),
Concrete::Threshold(2, key_pol[5..8].to_owned()),
]),
Arc::new(Concrete::And(vec![
Arc::new(Concrete::Older(Sequence::from_height(10000))),
Arc::new(Concrete::Threshold(
2,
key_pol[5..8].iter().map(|p| (p.clone()).into()).collect(),
)),
])),
),
]);

Expand Down Expand Up @@ -1391,8 +1426,10 @@ mod tests {
// and to a ms thresh otherwise.
// k = 1 (or 2) does not compile, see https://github.com/rust-bitcoin/rust-miniscript/issues/114
for k in &[10, 15, 21] {
let pubkeys: Vec<Concrete<bitcoin::PublicKey>> =
keys.iter().map(|pubkey| Concrete::Key(*pubkey)).collect();
let pubkeys: Vec<Arc<Concrete<bitcoin::PublicKey>>> = keys
.iter()
.map(|pubkey| Arc::new(Concrete::Key(*pubkey)))
.collect();
let big_thresh = Concrete::Threshold(*k, pubkeys);
let big_thresh_ms: SegwitMiniScript = big_thresh.compile().unwrap();
if *k == 21 {
Expand All @@ -1419,18 +1456,18 @@ mod tests {
// or(thresh(52, [pubkey; 52]), thresh(52, [pubkey; 52])) results in a 3642-bytes long
// witness script with only 54 stack elements
let (keys, _) = pubkeys_and_a_sig(104);
let keys_a: Vec<Concrete<bitcoin::PublicKey>> = keys[..keys.len() / 2]
let keys_a: Vec<Arc<Concrete<bitcoin::PublicKey>>> = keys[..keys.len() / 2]
.iter()
.map(|pubkey| Concrete::Key(*pubkey))
.map(|pubkey| Arc::new(Concrete::Key(*pubkey)))
.collect();
let keys_b: Vec<Concrete<bitcoin::PublicKey>> = keys[keys.len() / 2..]
let keys_b: Vec<Arc<Concrete<bitcoin::PublicKey>>> = keys[keys.len() / 2..]
.iter()
.map(|pubkey| Concrete::Key(*pubkey))
.map(|pubkey| Arc::new(Concrete::Key(*pubkey)))
.collect();

let thresh_res: Result<SegwitMiniScript, _> = Concrete::Or(vec![
(1, Concrete::Threshold(keys_a.len(), keys_a)),
(1, Concrete::Threshold(keys_b.len(), keys_b)),
(1, Arc::new(Concrete::Threshold(keys_a.len(), keys_a))),
(1, Arc::new(Concrete::Threshold(keys_b.len(), keys_b))),
])
.compile();
let script_size = thresh_res.clone().and_then(|m| Ok(m.script_size()));
Expand All @@ -1443,8 +1480,10 @@ mod tests {

// Hit the maximum witness stack elements limit
let (keys, _) = pubkeys_and_a_sig(100);
let keys: Vec<Concrete<bitcoin::PublicKey>> =
keys.iter().map(|pubkey| Concrete::Key(*pubkey)).collect();
let keys: Vec<Arc<Concrete<bitcoin::PublicKey>>> = keys
.iter()
.map(|pubkey| Arc::new(Concrete::Key(*pubkey)))
.collect();
let thresh_res: Result<SegwitMiniScript, _> =
Concrete::Threshold(keys.len(), keys).compile();
let n_elements = thresh_res
Expand All @@ -1462,8 +1501,10 @@ mod tests {
fn shared_limits() {
// Test the maximum number of OPs with a 67-of-68 multisig
let (keys, _) = pubkeys_and_a_sig(68);
let keys: Vec<Concrete<bitcoin::PublicKey>> =
keys.iter().map(|pubkey| Concrete::Key(*pubkey)).collect();
let keys: Vec<Arc<Concrete<bitcoin::PublicKey>>> = keys
.iter()
.map(|pubkey| Arc::new(Concrete::Key(*pubkey)))
.collect();
let thresh_res: Result<SegwitMiniScript, _> =
Concrete::Threshold(keys.len() - 1, keys).compile();
let ops_count = thresh_res.clone().and_then(|m| Ok(m.ext.ops.op_count()));
Expand All @@ -1475,8 +1516,10 @@ mod tests {
);
// For legacy too..
let (keys, _) = pubkeys_and_a_sig(68);
let keys: Vec<Concrete<bitcoin::PublicKey>> =
keys.iter().map(|pubkey| Concrete::Key(*pubkey)).collect();
let keys: Vec<Arc<Concrete<bitcoin::PublicKey>>> = keys
.iter()
.map(|pubkey| Arc::new(Concrete::Key(*pubkey)))
.collect();
let thresh_res = Concrete::Threshold(keys.len() - 1, keys).compile::<Legacy>();
let ops_count = thresh_res.clone().and_then(|m| Ok(m.ext.ops.op_count()));
assert_eq!(
Expand All @@ -1488,8 +1531,9 @@ mod tests {

// Test that we refuse to compile policies with duplicated keys
let (keys, _) = pubkeys_and_a_sig(1);
let key = Concrete::Key(keys[0]);
let res = Concrete::Or(vec![(1, key.clone()), (1, key.clone())]).compile::<Segwitv0>();
let key = Arc::new(Concrete::Key(keys[0]));
let res =
Concrete::Or(vec![(1, Arc::clone(&key)), (1, Arc::clone(&key))]).compile::<Segwitv0>();
assert_eq!(
res,
Err(CompilerError::PolicyError(policy::concrete::PolicyError::DuplicatePubKeys))
Expand Down
Loading

0 comments on commit c42b1cb

Please sign in to comment.