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

Tr compiler v3 - Incremental Enumerative Compiler #418

Merged
merged 2 commits into from
Sep 28, 2022

Conversation

SarcasticNastik
Copy link
Contributor

@SarcasticNastik SarcasticNastik commented May 25, 2022

This PR is a follow-up in the Tr-compiler series. This introduces splitting of a thresh(k, ...n...) policy into different Tapleaves.

For now, kindly refer to the code documentation and comments for the strategy implemented

@SarcasticNastik SarcasticNastik changed the title Tr compiler - Private thresh-enumeration [DO NOT MERGE]Tr compiler - Private thresh-enumeration May 25, 2022
@SarcasticNastik SarcasticNastik marked this pull request as draft May 25, 2022 03:25
@SarcasticNastik SarcasticNastik force-pushed the draft/tr-compiler-v3 branch 3 times, most recently from cb5cf91 to 3d473c9 Compare June 21, 2022 18:59
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.

Did some partial review.

@@ -278,6 +278,7 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
)),
_ => {
let (internal_key, policy) = self.clone().extract_key(unspendable_key)?;
policy.check_tapleaf()?;
Copy link
Member

Choose a reason for hiding this comment

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

Should update the name of the function. This implies that we can checking something on tapleafs. Perhaps, check_num_tapleaves?
I would prefer to inline the check_num_tapleaves here, but up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the function name is a misnomer. Will change the name as suggested.

/// Check on the number of TapLeaves
#[cfg(feature = "compiler")]
fn check_tapleaf(&self) -> Result<(), Error> {
if self.num_tap_leaves() > 100_000 {
Copy link
Member

Choose a reason for hiding this comment

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

Make this is local constant MAX_COMPILATION_TAP_LEAVES

#[cfg(feature = "compiler")]
fn check_tapleaf(&self) -> Result<(), Error> {
if self.num_tap_leaves() > 100_000 {
return Err(errstr("Too many Tapleaves"));
Copy link
Member

Choose a reason for hiding this comment

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

Let's use a dedicated error variant instead of errstr

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 still not addressed.

Ok(node)

#[cfg(feature = "compiler")]
fn combination(n: usize, k: usize) -> usize {
Copy link
Member

Choose a reason for hiding this comment

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

Whenever we call this, we need to make sure that we are having an overflow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function isn't supposed to be there. Seems I messed up while squashing the commits, my bad!

fn num_tap_leaves(&self) -> usize {
match self {
Policy::Or(subs) => subs.iter().map(|(_prob, pol)| pol.num_tap_leaves()).sum(),
Policy::Threshold(k, subs) if *k == 1 => combination(subs.len(), *k),
Copy link
Member

Choose a reason for hiding this comment

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

What happens when there is overflow here? We should use all the checked_sum, checked_mul variants here to check for this. Return Option where we return None when there is overflow

#[cfg(feature = "compiler")]
fn to_tapleaf_prob_vec(&self, prob: f64) -> Vec<(f64, Policy<Pk>)> {
match *self {
match self {
Policy::Or(ref subs) => {
let total_odds: usize = subs.iter().map(|(ref k, _)| k).sum();
Copy link
Member

Choose a reason for hiding this comment

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

remove the ref k here.

Copy link
Member

Choose a reason for hiding this comment

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

As in, remove the ref . It should clean up * (*k) code in the next line

let total_odds = subs.len();
subs.iter()
.map(|policy| policy.to_tapleaf_prob_vec(prob / total_odds as f64))
.flatten()
.collect::<Vec<_>>()
}
ref x => vec![(prob, x.clone())],
// Instead of the fixed-point algorithm, consider direct k-choose-n split if limits
Copy link
Member

Choose a reason for hiding this comment

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

I don't see why this comment is relevant here. This function is only splitting ors AFAIU.

/// Given a [`Policy`], return a vector of policies whose disjunction is isomorphic to the initial one.
/// This function is supposed to incrementally expand i.e. represent the policy as disjunction over
/// sub-policies output by it. The probability calculations are similar as
/// [to_tapleaf_prob_vec][`Policy::to_tapleaf_prob_vec`]
Copy link
Member

Choose a reason for hiding this comment

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

Why do you have the link twice?

Policy::Or(subs) => {
let total_odds = subs.iter().fold(0, |acc, x| acc + x.0);
subs.iter()
.map(|(odds, pol)| (prob * *odds as f64 / total_odds as f64, pol.clone()))
Copy link
Member

Choose a reason for hiding this comment

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

Should this not be recursive?

Policy::Threshold(k, subs) if *k == 1 => {
let total_odds = subs.len();
subs.iter()
.map(|pol| (prob / total_odds as f64, pol.clone()))
Copy link
Member

Choose a reason for hiding this comment

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

Same comment here

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.

code logic ACK, but would need some re-factors that will make it faster

@@ -50,3 +50,6 @@ pub const MAX_BLOCK_WEIGHT: usize = 4000000;
/// Maximum pubkeys as arguments to CHECKMULTISIG
// https://github.com/bitcoin/bitcoin/blob/6acda4b00b3fc1bfac02f5de590e1a5386cbc779/src/script/script.h#L30
pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20;

/// Maximum TapLeafs allowed in a compiled TapTree
pub(crate) const MAX_COMPILATION_LEAVES: usize = 10_000;
Copy link
Member

Choose a reason for hiding this comment

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

These constants are constants related to consensus standardness limits of bitcoin and are not specific to rust-miniscript. We should move MAX_COMPILATION_LEAVES to compiler.rs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we move it to concrete.rs since that's where the tr compiler is?

/// any one of the conditions exclusively.
#[cfg(feature = "compiler")]
fn generate_combination<Pk: MiniscriptKey>(
policy_vec: &Vec<Policy<Pk>>,
Copy link
Member

Choose a reason for hiding this comment

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

We should use Rc instead of cloning it here multiple times. This should help in space and time complexity

Copy link
Member

Choose a reason for hiding this comment

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

We would need a new data structure with Arc's here to store these instead of Policy. But we do not want so many clones here. This might be why everything is slow.

You can create something similar to Policy but with Arcs instead of Owned sub-policies.

@SarcasticNastik SarcasticNastik force-pushed the draft/tr-compiler-v3 branch 2 times, most recently from 1355757 to fee0f7c Compare August 6, 2022 14:36
@SarcasticNastik
Copy link
Contributor Author

@sanket1729 I've added a new structure PolicyArc<Pk> according to your suggestions. But I didn't find any significant improvements for the compilation time.

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.

mostly ACK. found a bug when dealing with more than MAX_COMPILATION_LEAVES part.

See the last commit here https://github.com/sanket1729/rust-miniscript/tree/pr418.

We are really close to merging this. I think you would run in --release mode to see the performance difference.

}

// --- Sanity Checks ---
if enum_len > MAX_COMPILATION_LEAVES || *curr_policy == PolicyArc::Unsatisfiable {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do not enumerate the sub-policy under consideration since we either exceed MAX_COMPILATION_LEAVES or the policy itself compiled to Unsatisfiable.

Copy link
Member

Choose a reason for hiding this comment

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

This was causing bugs because the enumeration was incorrect. I think the logic here would need to be improved. If you replace thresh(51,...) with thresh(49) you would see a crash

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a major bug. I've fixed it in the 9c8a1c3.

@SarcasticNastik SarcasticNastik changed the title [DO NOT MERGE]Tr compiler - Private thresh-enumeration Tr compiler v3 - Incremental Enumerative Compiler Aug 11, 2022
@SarcasticNastik SarcasticNastik marked this pull request as ready for review August 11, 2022 12:51
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.

Last few comments.

#[cfg(feature = "compiler")]
fn check_tapleaf(&self) -> Result<(), Error> {
if self.num_tap_leaves() > 100_000 {
return Err(errstr("Too many Tapleaves"));
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 still not addressed.

if enum_len > MAX_COMPILATION_LEAVES || *curr_policy == PolicyArc::Unsatisfiable {
// Root-level enumeration exceeds the limit, so push back the initial policy
if ret.is_empty() {
ret.push((1.0, arc_self));
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 not the expected behavior. We need to split as much as we can until we hit the limit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The variable enum_len stores the vector length if we consider the enumeration of plausible node. If this exceeds limit, we don't replace the initial plausible sub-policy by its enumerations (which is done after the check). I just handled the base-case where the root-level enumeration itself exceeds the limit, where we just return the policy itself.

@SarcasticNastik SarcasticNastik force-pushed the draft/tr-compiler-v3 branch 6 times, most recently from a7b1e82 to 71e4f81 Compare August 20, 2022 19:30
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 f9901ab. Can you also rebase to latest master? I think we are good to go here

@sanket1729
Copy link
Member

sanket1729 commented Sep 16, 2022

Unless @kanishk779 found something else in review here.

@sanket1729
Copy link
Member

I will wait a couple of days for @kanishk779 to respond. Will merge this later

@sanket1729
Copy link
Member

@SarcasticNastik , can you also update the PR description?

sanket1729
sanket1729 previously approved these changes Sep 19, 2022
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 265b063

@sanket1729 sanket1729 added this to the 8.0.0 milestone Sep 19, 2022
@sanket1729 sanket1729 mentioned this pull request Sep 19, 2022
@sanket1729
Copy link
Member

@SarcasticNastik , sorry for this. But I promise, this would be last rebase

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 97de242

@sanket1729 sanket1729 merged commit 51d66d1 into rust-bitcoin:master Sep 28, 2022
apoelstra added a commit that referenced this pull request Oct 19, 2022
3f451db Release 8.0.0 (sanket1729)

Pull request description:

  Draft release PR. Still blocked on reviews of

  1) #418
  2) #450
  3) #461

ACKs for top commit:
  apoelstra:
    ACK 3f451db

Tree-SHA512: aa455fd57a894b1edb49b6273126a05c9d0897ca50ee572fa8a59b4d2a96e2013f2ed9ec4b96cd5e449ba5b5ee6bd32aa57da110df69bd35b8a970ccb1440002
sanket1729 added a commit to sanket1729/elements-miniscript that referenced this pull request Oct 21, 2022
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.

2 participants