-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Make store.Query use ics23-proofs #6315
Changes from 3 commits
f351903
9a7da75
8f13f42
d99a01a
65d6b53
7d0a551
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package iavl | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
ics23 "github.com/confio/ics23/go" | ||
"github.com/tendermint/tendermint/crypto/merkle" | ||
) | ||
|
||
const ProofOpIAVL = "iavlstore" | ||
|
||
type IAVLOp struct { | ||
Key []byte | ||
Proof *ics23.CommitmentProof | ||
} | ||
|
||
var _ merkle.ProofOperator = IAVLOp{} | ||
|
||
func NewIAVLOp(key []byte, proof *ics23.CommitmentProof) IAVLOp { | ||
return IAVLOp{ | ||
Key: key, | ||
Proof: proof, | ||
} | ||
} | ||
|
||
func IAVLOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This guy definitely warrants a godoc 👍 |
||
if pop.Type != ProofOpIAVL { | ||
return nil, errors.New(fmt.Sprintf("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVL)) | ||
} | ||
var op IAVLOp | ||
proof := &ics23.CommitmentProof{} | ||
err := proof.Unmarshal(pop.Data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
op.Proof = proof | ||
|
||
// Get Key from proof for now | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the logic comparisons to ics23 types, are you sure the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently op.Data just contains marshalled ics proof while the op has the key in a separate field. I extract key from proof here, though we may want to instead wrap the ics-proof in a proto struct containing proof and key and just directly unmarshal the proofoperator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading this again, I see that the marshalled version is just the commitment proof and the key is only cached in memory when decoding Yeah, please ignore my comment about the newtype, I was really worrying about the serialized version. |
||
if existProof, ok := op.Proof.Proof.(*ics23.CommitmentProof_Exist); !ok { | ||
op.Key = existProof.Exist.Key | ||
} else if nonexistProof, ok := op.Proof.Proof.(*ics23.CommitmentProof_Nonexist); !ok { | ||
op.Key = nonexistProof.Nonexist.Key | ||
} else { | ||
return nil, errors.New("Proof type unsupported") | ||
} | ||
return op, nil | ||
} | ||
|
||
func (op IAVLOp) GetKey() []byte { | ||
return op.Key | ||
} | ||
|
||
func (op IAVLOp) Run(args [][]byte) ([][]byte, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this is super confusing. Not from @AdityaSripal but from whoever designed this ProofOperator interface: https://github.com/tendermint/tendermint/blob/master/crypto/merkle/proof.go#L12-L18 It is totally unclear if it is checking absence or existence. And why it returns multiple roots. I think this ProofOp interface needs an overhaul as well, but I think he is implementing this properly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can see how iavl existence proofs require exactly one entry: https://github.com/tendermint/iavl/blob/c4c6383ddd25f984467f78516aca7089bf110c97/proof_iavl_value.go And absence proofs require exactly 0: https://github.com/tendermint/iavl/blob/c4c6383ddd25f984467f78516aca7089bf110c97/proof_iavl_absence.go#L62-L64 |
||
switch len(args) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both Existence and Nonexistence proofs are being handled by the same IAVLop here |
||
case 0: | ||
// Args are nil, so we verify the absence of the key. | ||
nonexistProof, ok := op.Proof.Proof.(*ics23.CommitmentProof_Nonexist) | ||
if !ok { | ||
return nil, errors.New("proof is not a nonexistence proof and args is nil") | ||
} | ||
|
||
root, err := nonexistProof.Nonexist.Left.Calculate() | ||
AdityaSripal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return nil, errors.New("could not calculate root from nonexistence proof") | ||
} | ||
|
||
absent := ics23.VerifyNonMembership(ics23.IavlSpec, root, op.Proof, op.Key) | ||
if !absent { | ||
return nil, errors.New(fmt.Sprintf("proof did not verify absence of key: %s", string(op.Key))) | ||
} | ||
|
||
return [][]byte{root}, nil | ||
|
||
case 1: | ||
// Args is length 1, verify existence of key with value args[0] | ||
existProof, ok := op.Proof.Proof.(*ics23.CommitmentProof_Exist) | ||
if !ok { | ||
return nil, errors.New("proof is not a existence proof and args is length 1") | ||
} | ||
// For subtree verification, we simply calculate the root from the proof and use it to prove | ||
// against the value | ||
root, err := existProof.Exist.Calculate() | ||
if err != nil { | ||
return nil, errors.New("could not calculate root from existence proof") | ||
} | ||
|
||
exists := ics23.VerifyMembership(ics23.IavlSpec, root, op.Proof, op.Key, args[0]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only iavl-specific code here is this Although that is for a future pr. It is fine as is now. |
||
if !exists { | ||
return nil, errors.New(fmt.Sprintf("proof did not verify existence of key %s with given value %x", op.Key, args[0])) | ||
} | ||
|
||
return [][]byte{root}, nil | ||
default: | ||
return nil, errors.New(fmt.Sprintf("args must be length 0 or 1, got: %d", len(args))) | ||
} | ||
} | ||
|
||
func (op IAVLOp) ProofOp() merkle.ProofOp { | ||
bz, err := op.Proof.Marshal() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note data just contains marshalled proof for now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is quite nice. This is perfect to just store the protobuf data. |
||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
return merkle.ProofOp{ | ||
Type: ProofOpIAVL, | ||
Key: op.Key, | ||
Data: bz, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,8 @@ import ( | |||||||||
"io" | ||||||||||
"sync" | ||||||||||
|
||||||||||
ics23iavl "github.com/confio/ics23-iavl" | ||||||||||
ics23 "github.com/confio/ics23/go" | ||||||||||
"github.com/pkg/errors" | ||||||||||
"github.com/tendermint/iavl" | ||||||||||
abci "github.com/tendermint/tendermint/abci/types" | ||||||||||
|
@@ -275,31 +277,36 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { | |||||||||
break | ||||||||||
} | ||||||||||
|
||||||||||
_, res.Value = tree.GetVersioned(key, res.Height) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note we get the value from res.Height, but below use the tree from the current height. |
||||||||||
if req.Prove { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd love to see this being implemented as follows:
Suggested change
This would reduce the indentation level and make the code more readable |
||||||||||
value, proof, err := tree.GetVersionedWithProof(key, res.Height) | ||||||||||
if err != nil { | ||||||||||
res.Log = err.Error() | ||||||||||
break | ||||||||||
} | ||||||||||
if proof == nil { | ||||||||||
// Proof == nil implies that the store is empty. | ||||||||||
if value != nil { | ||||||||||
panic("unexpected value for an empty proof") | ||||||||||
var commitmentProof *ics23.CommitmentProof | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: group vars :) |
||||||||||
var err error | ||||||||||
if res.Value != nil { | ||||||||||
// Only support query proof from MutableTree for now | ||||||||||
mtree, ok := tree.(*iavl.MutableTree) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd love to see |
||||||||||
if !ok { | ||||||||||
return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "store must contain iavl.MutableTree to return proof")) | ||||||||||
} | ||||||||||
} | ||||||||||
if value != nil { | ||||||||||
// value was found | ||||||||||
res.Value = value | ||||||||||
res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewValueOp(key, proof).ProofOp()}} | ||||||||||
commitmentProof, err = ics23iavl.CreateMembershipProof(mtree, req.Data) | ||||||||||
if err != nil { | ||||||||||
panic(fmt.Sprintf("unexpected value for empty proof: %s", err.Error())) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why we both panic and return and error. It seems like we should always return an error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should never happen, so I panic. Also believe this matches previous behavior |
||||||||||
} | ||||||||||
} else { | ||||||||||
// Only support query proof from MutableTree for now | ||||||||||
mtree, ok := tree.(*iavl.MutableTree) | ||||||||||
if !ok { | ||||||||||
return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "store must contain iavl.MutableTree to return proof")) | ||||||||||
} | ||||||||||
// value wasn't found | ||||||||||
res.Value = nil | ||||||||||
res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewAbsenceOp(key, proof).ProofOp()}} | ||||||||||
commitmentProof, err = ics23iavl.CreateNonMembershipProof(mtree, req.Data) | ||||||||||
if err != nil { | ||||||||||
panic(fmt.Sprintf("unexpected empty absence proof: %s", err.Error())) | ||||||||||
} | ||||||||||
} | ||||||||||
} else { | ||||||||||
_, res.Value = tree.GetVersioned(key, res.Height) | ||||||||||
op := NewIAVLOp(req.Data, commitmentProof) | ||||||||||
res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{op.ProofOp()}} | ||||||||||
} | ||||||||||
|
||||||||||
case "/subspace": | ||||||||||
var KVs []types.KVPair | ||||||||||
|
||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if it makes it easier, use a different proof type for existence and absence. So you don't have to type switch below. Makes more sense to this code, but not sure in how it is used externally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... if other people are in favor I may go for it, it's certainly doable. I agree it removes the type switch, but virtually everything else about how to proof is constructed is identical so I think having the switch statement here is fine