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

Describe conflicts when raising MiniscriptPropertyError #14

Merged
merged 2 commits into from
May 2, 2022

Conversation

stickies-v
Copy link
Contributor

Adds a list of type and property conflicts to make debugging easier. With the lambda function approach, it should remain pretty flexible to add other types of conflicts.

For example:

from bip380.miniscript.property import Property
p = Property("K")
p.check_valid()

raises

bip380.miniscript.errors.MiniscriptPropertyError: Conflicting types and properties: (not K or u), (not K or s)

Copy link
Owner

@darosior darosior left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! It reminds me we never perform this check anywhere for now.

Comment on lines 65 to 83
checks = [
lambda: (not self.z or not self.o),
lambda: (not self.n or not self.z),
lambda: (not self.V or not self.d),
lambda: (not self.K or self.u),
lambda: (not self.V or not self.u),
lambda: (not self.e or not self.f),
lambda: (not self.e or self.d),
lambda: (not self.V or not self.e),
lambda: (not self.d or not self.f),
lambda: (not self.V or self.f),
lambda: (not self.K or self.s),
lambda: (not self.z or self.m),
]
conflicts = [fn for fn in checks if not fn()]
if conflicts:
formatted_conflicts = " ".join([inspect.getsource(fn).strip().replace("self.", "").replace("lambda: ", "")
for fn in conflicts])[:-1]
raise MiniscriptPropertyError(f"Conflicting types and properties: {formatted_conflicts}")
Copy link
Owner

Choose a reason for hiding this comment

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

Nice hack with inspect :-). But i think it would be cleaner if we leveraged the string representation here? For instance (untested):

diff --git a/bip380/miniscript/property.py b/bip380/miniscript/property.py
index 2f30b05..fa0c251 100644
--- a/bip380/miniscript/property.py
+++ b/bip380/miniscript/property.py
@@ -60,27 +60,27 @@ class Property:
                     )
                 num_types += 1
 
-        # Check for conflicts in type & properties.
-
-        checks = [
-            lambda: (not self.z or not self.o),
-            lambda: (not self.n or not self.z),
-            lambda: (not self.V or not self.d),
-            lambda: (not self.K or self.u),
-            lambda: (not self.V or not self.u),
-            lambda: (not self.e or not self.f),
-            lambda: (not self.e or self.d),
-            lambda: (not self.V or not self.e),
-            lambda: (not self.d or not self.f),
-            lambda: (not self.V or self.f),
-            lambda: (not self.K or self.s),
-            lambda: (not self.z or self.m),
+        # Check for conflicts in type & properties. For each type checked, give
+        # the properties it must have and the one it must not have.
+        conflicts = [
+            ("K", "", "us"),
+            ("V", "de", "f"),
+            ("z", "o", "m"),
+            ("n", "z", ""),
+            ("e", "d", "f"),
+            ("d", "", "f"),
         ]
-        conflicts = [fn for fn in checks if not fn()]
-        if conflicts:
-            formatted_conflicts = " ".join([inspect.getsource(fn).strip().replace("self.", "").replace("lambda: ", "")
-                                            for fn in conflicts])[:-1]
-            raise MiniscriptPropertyError(f"Conflicting types and properties: {formatted_conflicts}")
+        for (typ, must_be, must_not_be) in conflicts:
+            if not getattr(self, typ):
+                continue
+            if not all(getattr(self, prop) for prop in must_be):
+                raise MiniscriptPropertyError(
+                    f"Conflicting types and properties: '{typ}' needs always be '{must_be}'"
+                )
+            if any(getattr(self, prop) for prop in must_not_be):
+                raise MiniscriptPropertyError(
+                    f"Conflicting types and properties: '{typ}' can't be any of '{must_not_be}'"
+                )
 
     def type(self):
         return "".join(filter(lambda x: x in self.types, str(self)))

@stickies-v
Copy link
Contributor Author

I've pushed "Use structured checks" that incorporates your suggestion, but I like it when errors contain as many detected problems as possible instead of just the first one so I modified that (and I think you had some of the must_be and must_not_be switched up - I'm just basing myself off the previous version, not sure how correct that is).

The reason I went for lambda & inspect is because I couldn't anticipate how much flexibility you'd like to keep with the conflict checking system. For example, with the lambda approach it would be trivial to add a check like "if (K and u), then must be s". If that flexibility is not (likely to be) necessary soon, then it would probably be better not to have to rely on the inspect module and have things a bit more structured. I think both approaches are sensible and don't really have a preference, so if you prefer the structured approach I'll squash those 2 commits and force push.

@darosior
Copy link
Owner

darosior commented May 1, 2022 via email

Copy link
Owner

@darosior darosior left a comment

Choose a reason for hiding this comment

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

ACK 96dcb1d

Comment on lines +53 to +54
if len(self.type()) > 1:
raise MiniscriptPropertyError("A Miniscript fragment can only be of a single type")
Copy link
Owner

Choose a reason for hiding this comment

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

Heh, nice 😅

@darosior darosior merged commit 8aa1e2f into darosior:master May 2, 2022
@stickies-v stickies-v deleted the add-conflicts branch May 2, 2022 08:38
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