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

refactoring the ics23 spec #975

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open

refactoring the ics23 spec #975

wants to merge 23 commits into from

Conversation

konnov
Copy link
Contributor

@konnov konnov commented Jun 23, 2023

This is a refactoring of the ICS23 spec, which took more effort than I expected (about 8hrs in total):

  • the spec is now split in multiple files,
  • the spec is now using the up-to-date syntax and idioms,
  • importantly, hashes are modelled as symbolic terms, see hashes.qnt
  • removed TLA+ files, as we do not want to maintain them in the long run,
  • removed the simple PBT tests, as they were not useful, in favor of tree generators treeGen.qnt.

Reviewing this specification is probably a big effort. So I would appreciate at least a quick glance, to check that nothing weird stands out.

Copy link
Contributor

@thpani thpani left a comment

Choose a reason for hiding this comment

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

First set of remarks. I've only made it through hashes.qnt so far.

examples/cosmos/ics23/README.md Show resolved Hide resolved
examples/cosmos/ics23/README.md Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
Copy link
Contributor

@thpani thpani left a comment

Choose a reason for hiding this comment

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

Wow 🤯
Second set of comments below:

Comment on lines 33 to 36
// ICS23 IavlSpec has:
// MinPrefixLength = 4
// MaxPrefixLength = 12
// ChildSize = 33
// MinPrefixLength = 1
// MaxPrefixLength = 1
// ChildSize = 32
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks to be the tendermint spec, but the comment says IavlSpec.
Can we confirm which one it is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, I always thought it Iavl, but seems to be Tendermint

Copy link
Contributor

Choose a reason for hiding this comment

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

seems to be Tendermint

Can we ask someone to confirm?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@romac and I had a look at the code this morning. Not easy to see, but both IavlSpec and TendermintSpec should work. Hugs to @romac for figuring that out. That code is beyond my comprehension 🍄

examples/cosmos/ics23/ics23.qnt Show resolved Hide resolved
examples/cosmos/ics23/treeGen.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/treeGen.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/treeGen.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/treeGen.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/treeGen.qnt Outdated Show resolved Hide resolved
examples/cosmos/ics23/treeGen.qnt Outdated Show resolved Hide resolved
@thpani
Copy link
Contributor

thpani commented Jul 26, 2023

I thought this was good to merge, but something in 60a2df4 broke the verifyNonMembershipTest in ics23test.

Copy link
Member

@josef-widder josef-widder left a comment

Choose a reason for hiding this comment

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

Looks super useful for testing. I guess now with the sum types, we could turn this spec more readable. I will reach out to the team to see how we can coordinate around merging it.

examples/cosmos/ics23/hashes.qnt Outdated Show resolved Hide resolved
Comment on lines +70 to +73
/// A word is a map from a path to the term node.
/// The first root's child is [ 0 ], the second root's child is [ 1 ],
/// the first child of [ 0 ] is [ 0, 0], etc.
type Term_t = Bytes_t -> TERM_NODE_T
Copy link
Member

Choose a reason for hiding this comment

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

For a binary tree, This would also be encoded today as sum type, e.g., a list of LeftNode, RightNode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have to note that Quint sum types are not inductive/recursive, for a reason of not admitting unbounded data structures. So it would not be possible to encode a tree with Quint sum types.

Copy link
Member

Choose a reason for hiding this comment

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

But now it is a list of integers, where only 0 and 1 is used. Why cannot it be a list of a sum type instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Try to write it in any language that has complete support for sum types, e.g., in OCaml. You will see that you need recursion. Basically, you have to say that a node refers to the children nodes (of the same type). If you use a mathematical function instead, e.g., $f: Paths \rightarrow Nodes$, you do not need an inductive definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A list of a sum type would not give you much, as you would not want a leaf to appear in the middle of the list.

Comment on lines +78 to +80
// the roots' children
pure val top =
keys(term).filter(p => length(p) == 1).map(p => term.get(p))
Copy link
Member

Choose a reason for hiding this comment

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

Root's children is checked with length(p) == 1. If this would be a defined predicate, perhaps it would be more readable.

} else {
// Merge the arguments as trees representing terms.
// The number of root's children in the left term:
pure val lwidth = size(keys(left).filter(p => length(p) == 1))
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure I understand what is going on here. Does this only work if left and right don't contain an entry for [1]? Otherwise the expression lwidth + p[0] below would suddenly introduce keys whose value is greater than 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I remember, every node is encoded as the path to this node. For instance, [0, 1, 0, 1] in a binary tree would mean that you first go left, then right, then left, then right. So if we count the number of paths of length 1, then we get the number of root's children.

Copy link
Member

Choose a reason for hiding this comment

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

I agree, but if we have 2 root's children, we get lwidth = 2, but then two lines below we have

 keys(right).map(p => [ lwidth + p[0] ].concat(p.slice(1, length(p))))

So it seems that I could get a 2 and a 3 as first list entry, and this is what I don't understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That definition merges two trees. Say, if you have root's children [0, 1] in tree 1 and root's children [0, 1] in tree 2, then you would like to have root's children [0, 1, 2, 3]. This is what addition does for you: It simply renames the immediate root's children in tree 2, for all paths in that tree.

Copy link
Member

Choose a reason for hiding this comment

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

That makes sense. I just thought we would stay with binary trees...

Copy link
Contributor

@shonfeder shonfeder left a comment

Choose a reason for hiding this comment

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

Clearing my name from the review requests here.

Hope all is well! <3

@bugarela
Copy link
Collaborator

@josef-widder I realize now that the reason why we can't merge this is that a test is failing.

$ quint test ics23test.qnt --verbosity=5

  ics23test
    ok existsCalculateTest passed 1 test(s)
    ok isLeftMost1Test passed 1 test(s)
    ok isLeftMost2Test passed 1 test(s)
    ok isLeftMost3Test passed 1 test(s)
    ok isRightMost1Test passed 1 test(s)
    ok isRightMost2Test passed 1 test(s)
    ok isLeftStep1Test passed 1 test(s)
    ok isLeftNeighborTest passed 1 test(s)
    1) verifyNonMembershipTest failed after 1 test(s)

  8 passing (31ms)
  1 failed

  1) verifyNonMembershipTest:
      /home/gabriela/projects/quint/examples/cosmos/ics23/ics23test.qnt:136:7 - error: [QNT508] Assertion failed
      136:       assert(verifyNonMembership(root, np1, [ 1 ])),

[Frame 0]
verifyNonMembership(
  Map(
    [0, 0] -> { b: [0, 1, 2, 3, 32], t: "r" },
    [0, 1, 0] -> { b: [0, 1, 2, 3, 1, 2, 32], t: "r" },
    [0, 1, 1, 0] -> { b: [3], t: "r" },
    [0, 1, 1] -> { b: [], t: "h" },
    [0, 1] -> { b: [], t: "h" },
    [0, 2, 0] -> { b: [0, 1, 2, 3, 1, 4, 32], t: "r" },
    [0, 2, 1, 0] -> { b: [5], t: "r" },
    [0, 2, 1] -> { b: [], t: "h" },
    [0, 2] -> { b: [], t: "h" },
    [0] -> { b: [], t: "h" }
  ),
  {
    key: [1],
    left: { key: [], leaf: { prefix: Map([0] -> { b: [], t: "r" }) }, path: [], value: [] },
    right:
      {
        key: [2],
        leaf: { prefix: Map([0] -> { b: [0, 1, 2, 3], t: "r" }) },
        path:
          [
            {
              prefix: Map([0] -> { b: [0, 1, 2, 3], t: "r" }),
              suffix: Map([0] -> { b: [32], t: "r" }, [1, 0] -> { b: [0, 1, 2, 3, 1, 4, 32], t: "r" }, [1, 1, 0] -> { b: [5], t: "r" }, [1, 1] -> { b: [], t: "h" }, [1] -> { b: [], t: "h" })
            }
          ],
        value: [3]
      }
  },
  [1]
) => false

I've inspected the commits since the last time it was passing, and I suspect that this is the problematic bit: 2335cf9.

Maybe this test needed to be updated according to the new representation including the length marker byte, but wasn't - I'm not sure, as I don't really understand what is going on here. Do you have a sense of the problem?

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.

6 participants