Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TreeLike for policy::Concrete #594

Merged
merged 16 commits into from
Sep 27, 2023

Conversation

tcharding
Copy link
Member

@tcharding tcharding commented Sep 2, 2023

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

  • Patch 1: RemovePolicyArc
  • 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.

@tcharding tcharding changed the title Implement TreeLike for policy::Concrete Implement TreeLike for policy::Concrete Sep 2, 2023
@tcharding tcharding marked this pull request as ready for review September 4, 2023 14:02
@tcharding
Copy link
Member Author

Unless I'm missing something, if this goes in, we can remove the concrete::PolicyArc type (which is now a exactly the same as concrete::Policy)

@tcharding
Copy link
Member Author

@apoelstra hats off to your iter module, that tree is hot.

@tcharding tcharding marked this pull request as draft September 4, 2023 19:07
@apoelstra
Copy link
Member

Are you able to split this into two commits: one that drops PolicyArc in favor of tweaking Policy and one which implements TreeLike?

74cafab looks good to me.

@tcharding
Copy link
Member Author

Can do!

@tcharding tcharding marked this pull request as ready for review September 21, 2023 23:40
@tcharding
Copy link
Member Author

Are you able to split this into two commits: one that drops PolicyArc in favor of tweaking Policy and one which implements TreeLike?

I did that then went a bit wild using TreeLike :)

Policy::Threshold(_k, ref subs) => {
subs.iter().flat_map(|sub| sub.keys()).collect::<Vec<_>>()
let mut keys = vec![];
for data in self.post_order_iter() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 062e400:

I think we should use the pre-order iterator here, which is more intuitively correct. (In general, the post-order iterator matches recursive logic, but I think that, since we're only counting leaf nodes, the two iterators will actually have the same effect.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my bad, I had it in my head that the post order iter was the simpler of the two, I was wrong. Defaulting to pre-order iter when we don't need references to the child nodes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, post-order is definitely the most common because it allows you to simulate depth-first recursive algorithms. But conceptually I think pre-order is more natural.

Threshold(k, subs) if *k == 1 => (0..subs.len()).map(num_for_child_n).sum(),
_ => 1,
};
nums.push(num);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 7e73ffe:

We don't need to maintain a vector of numbers here. We can just do a running sum and avoid some allocations and unwraps.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we can, don't we need to sum the results of nested Ors? I don't think we can do that without recursion or keeping the nums vec. Am I correct in thinking a tree like this should return 4

        or
      /     \ 
   or       or
/ \   /\  /\  /\
k  k k k  k  k  k

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you're right, I looked at the algorithm too quickly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No sweat, it made me double check it which was not a bad thing :)

if let Policy::Key(ref pk) = data.node {
if !pred(pk) {
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 37f3796:

I also think this one should use pre-order iter; I also think it makes no difference here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@tcharding
Copy link
Member Author

tcharding commented Sep 23, 2023

Changes in force push:

  • Used pre_order_iter where appropriate
  • Got a bit more creative with combinators, if you do not have to look up exactly what all an filter_map do then you're a boss, I still have to look them up.

apoelstra
apoelstra previously approved these changes Sep 23, 2023
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK f145ac0

The `concrete::PolicyArc` type is identical to `Policy` except that it
uses `Arc` to wrap the nested `Policy` structs.

In preparation for implementing `TreeLike` and removing recursive
functions that process the `Policy` type remove the `PolicyArc` and add
`Arc` wrappers around the nested `Policy` fields.

Note, this patch does a bunch on cloning in the recursive functions.
This will dissapear when they are re-written to use tree iteration.

There are more optimizaitons available by taking `Arc<Policy<Pk>>` as a
parameter to functions in the `compiler` module, left for later.

The unit tests are bit klunky, much mapping of iterators to add `Arc`, I
think this will come out in the wash as we keep working.
In preparation for removing recursion in the `policy::concrete` module
implement `TreeLike` for `policy::Concrete` (reference, and `Arc`).
We are about to start using the `TreeLike` trait everywhere, hence it
will become an easily recognizable pattern. The current single case has
a code comment that is noisy if duplicated many times.
In preparation for modifying the `translate_unsatisfiable_pk` function
add a basic unit test.
We just implemented `TreeLike` for `concrete::Policy`, use it to
implement `translate_unsatisfiable_pk`, removing recursive calls
from the function.
In preparation for modifying the `keys` function add a basic unit test.
Remove recursive calls and use `TreeLike`'s post order iterator to get
all the keys from a `concrete::Policy`.
In preparation for modifying the `num_tap_leaves` function add a basic
unit test.
Remove recursive calls and use `TreeLike`'s post order iterator to get
the total number of tap leaves for a `concrete::Policy`.
In preparation for modifying the `for_each_key` add an additional unit
test that checks we correctly handle a failing predicate. While we are
at it rename the existing test.
Remove recursive calls and use `TreeLike`'s post order iterator to
implement `for_each_key` for the `concrete::Policy`.

No additional unit tests required, this code path is already tested.
In preparation for modifying the `translate_pk` function add a basic
unit test.
Remove recursive calls and use `TreeLike`'s post order iterator to
implement `translate_pk` for the `concrete::Policy`.

Note, no additional unit tests added for this code path, this is a
familiar code pattern now.
Add a unit test that attempts to parse a `concrete::Policy` with both
absolute height and time locks.

Done in preparation for patching `check_timelocks_helper`.
Remove recursive calls and use `TreeLike`'s post order iterator to
implement `check_timelocks_helper` for the `concrete::Policy`.

Note, no additional unit tests added for this code path, this is a
familiar code pattern now.
Rename the helper function and local variable at its call site to better
describe the functionality.

Refactor only, no logic changes.
@tcharding
Copy link
Member Author

Rebase and run formatter on each commit.

/// A list of sub-policies, one of which must be satisfied, along with
/// relative probabilities for each one.
Or(Vec<(usize, Policy<Pk>)>),
Or(Vec<(usize, Arc<Policy<Pk>>)>),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/me gently banging head on table crying, "please god no, don't let this be a vector when it is only ever exactly 2 policies"

@apoelstra or @sanket1729 are Or and And policies only ever comprised of exactly 2 sub-policies?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might have such a restriction right now (you'll find some panics enforcing it) but we intend to lift this restriction eventually.

The main issue is the computational complexity of compiling n-ary ors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we intend to lift this restriction eventually.

By we do you mean miniscript in general or rust-miniscript? From the miniscript docs it looks like its limited to 2 also but I couldn't tell from the syntax if that was guaranteed. Is it ambiguous for the same reason?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miniscript in general.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, so leave these as vecs then. Perhaps I'll add some comments. I literally nearly fell apart yesterday, after 3 days of mental modelling of vectors and tree iteration to find this "2 limit". I'm better today :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to hear it :). Sure, some comments would be appreciated.

// Currently these vectors are limited to two elements. Eventually we would like to extend these
// to be n-ary, but first we need to decide on a game plan for how to efficiently compile n-ary
// disjunctions

or something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no such length restrictions on the Policy::Threshold so when k == 1 we essentially have unbounded Or and when k == v.len() we have unbounded And - should we be restricting the length of the vector in these two cases also?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FTR I'm playing around with creating a Thresh<T> type to maintain the invariants v.len() > 0, k != 0, k <= v.len()`.

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 1efc7a3

Copy link
Member

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 1efc7a3

@@ -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> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really not a concern for this PR, but I think policy stuff should belong in policy module. Not here. See #603 for more.

@sanket1729 sanket1729 merged commit c42b1cb into rust-bitcoin:master Sep 27, 2023
16 checks passed
@tcharding tcharding deleted the 09-02-policy-for-each-key branch September 28, 2023 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants