-
Notifications
You must be signed in to change notification settings - Fork 466
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
PlutusLedgerApi.V1.Interval.contains
is wrong for discrete intervals
#5185
Labels
Comments
effectfully
added
status: needs action from the team
and removed
status: needs triage
GH issues that requires triage
labels
Mar 1, 2023
Thank you very much for such a well-written report. We're already working on the issue. |
michaelpj
added a commit
that referenced
this issue
Mar 2, 2023
`Interval` was not in a good place. It violated a bunch of laws. The first thing this PR does is add a bunch more tests: - Generic tests for the typeclasses that `Interval` has instances of. - Some specific tests that test that finite intervals behave the same as sets of consecutive numbers. The latter gives us a "semantic" test that lets us check whether our operations on intervals are doing the right thing with respect to a model - in this case the model is the set of values that fall into the inteval. Then we need to actually fix `Interval`. The most straightforward solution I could see is just to restrict most of its operations to intervals on enumerable types. That lets us turn non-inclusive bounds into inclusive bounds, which simplifies things a lot. Without that information, we can't easily even tell if e.g. an interval is empty or not. Arguably we should just have never had non-inclusive bounds in the first place and it's unnecessary generality. However, I think changing that now would be more disruptive than it is worth. Fixes #5185
michaelpj
added a commit
that referenced
this issue
Mar 2, 2023
`Interval` was not in a good place. It violated a bunch of laws. The first thing this PR does is add a bunch more tests: - Generic tests for the typeclasses that `Interval` has instances of. - Some specific tests that test that finite intervals behave the same as sets of consecutive numbers. The latter gives us a "semantic" test that lets us check whether our operations on intervals are doing the right thing with respect to a model - in this case the model is the set of values that fall into the inteval. Then we need to actually fix `Interval`. The most straightforward solution I could see is just to restrict most of its operations to intervals on enumerable types. That lets us turn non-inclusive bounds into inclusive bounds, which simplifies things a lot. Without that information, we can't easily even tell if e.g. an interval is empty or not. Arguably we should just have never had non-inclusive bounds in the first place and it's unnecessary generality. However, I think changing that now would be more disruptive than it is worth. Fixes #5185
michaelpj
added a commit
that referenced
this issue
Mar 2, 2023
`Interval` was not in a good place. It violated a bunch of laws. The first thing this PR does is add a bunch more tests: - Generic tests for the typeclasses that `Interval` has instances of. - Some specific tests that test that finite intervals behave the same as sets of consecutive numbers. The latter gives us a "semantic" test that lets us check whether our operations on intervals are doing the right thing with respect to a model - in this case the model is the set of values that fall into the inteval. Then we need to actually fix `Interval`. The most straightforward solution I could see is just to restrict most of its operations to intervals on enumerable types. That lets us turn non-inclusive bounds into inclusive bounds, which simplifies things a lot. Without that information, we can't easily even tell if e.g. an interval is empty or not. Arguably we should just have never had non-inclusive bounds in the first place and it's unnecessary generality. However, I think changing that now would be more disruptive than it is worth. Fixes #5185
@carlhammann, the issue is resolved, thanks again for the fantastic report! |
Yep, this was gold. |
Thanks for the kind words, guys! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Summary
Hello!
The problem
I expect$\supseteq$ on intervals. For discrete intervals, this expectation is violated when comparing (some) open and closed intervals.
contains
to check the relationFor instance, for intervals of integers,$[0,2]=\{0,1,2\}=[0,3)$ , and in particular $[0,2]\supseteq[0,3)$ , but
evaluates to
False
.This has confusing consequences when applied to, say, the
txInfoValidRange
, which is a (discrete)Interval
of milliseconds. Here is an example of a validator that checks that a transaction happens before a given deadline. It does so in the obvious way, and is subtly wrong in doing so, due to the behaviour ofcontains
.The cause
Following the implementation, and in particular the
Ord
instance ofUpperBound
, we see that for all integersa
andb
The wrong step here is
(UpperBound b False <= UpperBound (b-1) True) == False
. It arises because theOrd
instance ofUpperBound
first compares the bounds, and only if they are equal checks whether they are inclusive or not. The wrong assumption it makes is that "ifb
is bigger thanb-1
, an interval ending atb
cannot be contained in an interval ending atb-1
". This holds in the continuous case, but not in the discrete case.The
Ord
instance ofLowerBound
makes the same mistake, mutatis mutandis.A bonus bug
contains
is also wrong when it comes to empty sets. It holdsevaluates to
False
.Steps to reproduce the behavior
import Plutus.V1.Ledger.Interval
Actual Result
If one reads$\supseteq$ , and does the appropriate translations of open and closed intervals, inclusions of the forms
contains
asall evaluate to
False
onInteger
.Expected Result
The inclusions above should all evaluate to
True
.Describe the approach you would take to fix this
If I'm not mistaken, there are no uses of$\pm\infty$ .
Interval a
with "continuous"a
. Should that be the case, a solution would be to haveInterval a
be isomorphic to(Maybe a, Maybe b)
, where everyJust
denotes an included bound, andNothing
stands forIf there are uses for intervals of "continuous" element types, one could implement a function like
containsDiscrete :: (Ord a, Enum a) => Interval a -> Interval a -> Bool
to do the right thing in the discrete case. (Edit:Enum
is probably not a good idea, since it relies onInt
, which may be too small...)In any case, the problem with empty sets is easy to solve by adding an emptyness check for the second argument to the implementation of
contains
.System info
irrelevant
The text was updated successfully, but these errors were encountered: