From 5f0a378b96950b11aaa9a5ce87de63295540a9ea Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 2 Jun 2020 15:38:30 -0400 Subject: [PATCH 01/13] switch iavl store to use ics proof --- go.mod | 2 + go.sum | 8 +++ store/iavl/proof.go | 122 ++++++++++++++++++++++++++++++++++ store/iavl/store.go | 55 +++++++++------ store/rootmulti/proof.go | 6 +- store/rootmulti/proof_test.go | 30 --------- 6 files changed, 169 insertions(+), 54 deletions(-) create mode 100644 store/iavl/proof.go diff --git a/go.mod b/go.mod index 05057cbdbf29..fe421d8b1629 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ require ( github.com/bgentry/speakeasy v0.1.0 github.com/btcsuite/btcd v0.20.1-beta github.com/btcsuite/btcutil v1.0.2 + github.com/confio/ics23-iavl v0.6.0 + github.com/confio/ics23/go v0.0.0-20200325200809-9f53dd0c4212 github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d github.com/cosmos/ledger-cosmos-go v0.11.1 github.com/gibson042/canonicaljson-go v1.0.3 diff --git a/go.sum b/go.sum index 507be717e0b5..e21d72f672a0 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/confio/ics23 v0.6.0 h1:bQsi55t2+xjW6EWDl83IBF1VWurplbUu+OT6pukeiEo= +github.com/confio/ics23-iavl v0.6.0 h1:vVRCuVaP38FCw1kTeEdFuGuiY+2vAGTBQoH7Zxkq/ws= +github.com/confio/ics23-iavl v0.6.0/go.mod h1:mmXAxD1vWoO0VP8YHu6mM1QHGv71NQqa1iSVm4HeKcY= +github.com/confio/ics23/go v0.0.0-20200323120010-7d9a00f0a2fa/go.mod h1:W1I3XC8d9N8OTu/ct5VJ84ylcOunZwMXsWkd27nvVts= +github.com/confio/ics23/go v0.0.0-20200325200809-9f53dd0c4212 h1:MgS8JP5m7fPl7kumRm+YyAe5le3JlwQ4n5T/JXvr36s= +github.com/confio/ics23/go v0.0.0-20200325200809-9f53dd0c4212/go.mod h1:W1I3XC8d9N8OTu/ct5VJ84ylcOunZwMXsWkd27nvVts= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -476,6 +482,7 @@ github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= github.com/tendermint/go-amino v0.15.1 h1:D2uk35eT4iTsvJd9jWIetzthE5C0/k2QmMFkCN+4JgQ= github.com/tendermint/go-amino v0.15.1/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/iavl v0.13.2/go.mod h1:vE1u0XAGXYjHykd4BLp8p/yivrw2PF1TuoljBcsQoGA= github.com/tendermint/iavl v0.13.3 h1:expgBDY1MX+6/3sqrIxGChbTNf9N9aTJ67SH4bPchCs= github.com/tendermint/iavl v0.13.3/go.mod h1:2lE7GiWdSvc7kvT78ncIKmkOjCnp6JEnSb2O7B9htLw= github.com/tendermint/tendermint v0.33.2 h1:NzvRMTuXJxqSsFed2J7uHmMU5N1CVzSpfi3nCc882KY= @@ -483,6 +490,7 @@ github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICf github.com/tendermint/tendermint v0.33.5 h1:jYgRd9ImkzA9iOyhpmgreYsqSB6tpDa6/rXYPb8HKE8= github.com/tendermint/tendermint v0.33.5/go.mod h1:0yUs9eIuuDq07nQql9BmI30FtYGcEC60Tu5JzB5IezM= github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY= +github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U= github.com/tendermint/tm-db v0.5.1 h1:H9HDq8UEA7Eeg13kdYckkgwwkQLBnJGgX4PgLJRhieY= github.com/tendermint/tm-db v0.5.1/go.mod h1:g92zWjHpCYlEvQXvy9M168Su8V1IBEeawpXVVBaK4f4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= diff --git a/store/iavl/proof.go b/store/iavl/proof.go new file mode 100644 index 000000000000..14707d2c6e54 --- /dev/null +++ b/store/iavl/proof.go @@ -0,0 +1,122 @@ +package iavl + +import ( + "errors" + "fmt" + + ics23 "github.com/confio/ics23/go" + "github.com/tendermint/tendermint/crypto/merkle" +) + +const ProofOpIAVL = "iavlstore" + +// IavlOP implements merkle.ProofOperator by wrapping an ics23 CommitmentProof +// It also contains a Key field to determine which key the proof is proving. +// NOTE: CommitmentProof currently can either be ExistenceProof or NonexistenceProof +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, + } +} + +// IAVLOpDecoder takes a merkle.ProofOp and attempt to decode it into a IAVLOp ProofOperator +// The proofOp.Data is just a marshalled CommitmentProof. The Key of the IAVLOp is extracted +// from the unmarshalled proof +func IAVLOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { + 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 + 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) { + // Only support an existence proof or nonexistence proof (batch proofs currently unsupported) + switch len(args) { + 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") + } + + // get root from either left or right existence proof. Note they must have the same root if both exist + // and at least one proof must be non-nil + root, err := nonexistProof.Nonexist.Left.Calculate() + if err != nil { + // Left proof may be nil, check right proof + root, err = nonexistProof.Nonexist.Right.Calculate() + 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]) + 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() + if err != nil { + panic(err.Error()) + } + return merkle.ProofOp{ + Type: ProofOpIAVL, + Key: op.Key, + Data: bz, + } +} diff --git a/store/iavl/store.go b/store/iavl/store.go index 3cc2a83b5ce1..67723f91d5fa 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -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,42 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { break } - if req.Prove { - 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") - } + _, res.Value = tree.GetVersioned(key, res.Height) + if !req.Prove { + break + } + // Continue to prove existence/absence of value + var commitmentProof *ics23.CommitmentProof + var err error + + // Must convert store.Tree to iavl.MutableTree to use in CreateProof + var mtree *iavl.MutableTree + switch t := tree.(type) { + case *iavl.MutableTree: + mtree = t + case *immutableTree: + mtree = &iavl.MutableTree{ + ImmutableTree: t.ImmutableTree, } - if value != nil { - // value was found - res.Value = value - res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewValueOp(key, proof).ProofOp()}} - } else { - // value wasn't found - res.Value = nil - res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewAbsenceOp(key, proof).ProofOp()}} + default: + return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "store must contain iavl.MutableTree or iavl.ImmutableTree to return proof")) + } + + if res.Value != nil { + // value was found + commitmentProof, err = ics23iavl.CreateMembershipProof(mtree, req.Data) + if err != nil { + panic(fmt.Sprintf("unexpected value for empty proof: %s", err.Error())) } } else { - _, res.Value = tree.GetVersioned(key, res.Height) + // value wasn't found + commitmentProof, err = ics23iavl.CreateNonMembershipProof(mtree, req.Data) + if err != nil { + panic(fmt.Sprintf("unexpected empty absence proof: %s", err.Error())) + } } - + op := NewIAVLOp(req.Data, commitmentProof) + res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{op.ProofOp()}} case "/subspace": var KVs []types.KVPair diff --git a/store/rootmulti/proof.go b/store/rootmulti/proof.go index af0f919c367b..80915b587ebb 100644 --- a/store/rootmulti/proof.go +++ b/store/rootmulti/proof.go @@ -5,8 +5,9 @@ import ( "errors" "fmt" - "github.com/tendermint/iavl" "github.com/tendermint/tendermint/crypto/merkle" + + iavlstore "github.com/cosmos/cosmos-sdk/store/iavl" ) // MultiStoreProof defines a collection of store proofs in a multi-store @@ -128,8 +129,7 @@ func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) { func DefaultProofRuntime() (prt *merkle.ProofRuntime) { prt = merkle.NewProofRuntime() prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder) - prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.ValueOpDecoder) - prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.AbsenceOpDecoder) + prt.RegisterOpDecoder(iavlstore.ProofOpIAVL, iavlstore.IAVLOpDecoder) prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder) return } diff --git a/store/rootmulti/proof_test.go b/store/rootmulti/proof_test.go index d78886a88f22..edb592a11519 100644 --- a/store/rootmulti/proof_test.go +++ b/store/rootmulti/proof_test.go @@ -109,36 +109,6 @@ func TestVerifyMultiStoreQueryProof(t *testing.T) { require.NotNil(t, err) } -func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { - // Create main tree for testing. - db := dbm.NewMemDB() - store := NewStore(db) - iavlStoreKey := types.NewKVStoreKey("iavlStoreKey") - - store.MountStoreWithDB(iavlStoreKey, types.StoreTypeIAVL, nil) - err := store.LoadVersion(0) - require.NoError(t, err) - cid := store.Commit() // Commit with empty iavl store. - - // Get Proof - res := store.Query(abci.RequestQuery{ - Path: "/iavlStoreKey/key", // required path to get key/value+proof - Data: []byte("MYKEY"), - Prove: true, - }) - require.NotNil(t, res.Proof) - - // Verify proof. - prt := DefaultProofRuntime() - err = prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY") - require.Nil(t, err) - - // Verify (bad) proof. - prt = DefaultProofRuntime() - err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) - require.NotNil(t, err) -} - func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { // Create main tree for testing. db := dbm.NewMemDB() From 3f386990ceda5a3df95d9dede2ca438376730e2c Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 2 Jun 2020 15:47:57 -0400 Subject: [PATCH 02/13] fix proofs to return for correct height --- store/iavl/store.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/store/iavl/store.go b/store/iavl/store.go index 67723f91d5fa..3b9a2bbaf71a 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -285,17 +285,13 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { var commitmentProof *ics23.CommitmentProof var err error - // Must convert store.Tree to iavl.MutableTree to use in CreateProof - var mtree *iavl.MutableTree - switch t := tree.(type) { - case *iavl.MutableTree: - mtree = t - case *immutableTree: - mtree = &iavl.MutableTree{ - ImmutableTree: t.ImmutableTree, - } - default: - return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "store must contain iavl.MutableTree or iavl.ImmutableTree to return proof")) + // Must convert store.Tree to iavl.MutableTree with given version to use in CreateProof + iTree, err := tree.GetImmutable(res.Height) + if err != nil { + panic("value at height was retrieved successfully without corresponding versioned tree in store") + } + mtree := &iavl.MutableTree{ + ImmutableTree: iTree, } if res.Value != nil { From 6eae708c275d4006d9d2130d1b7ad5d54f6cf260 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 2 Jun 2020 16:57:02 -0400 Subject: [PATCH 03/13] appease linter --- store/iavl/proof.go | 38 +++++++++++++++++++------------------- store/iavl/store.go | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/store/iavl/proof.go b/store/iavl/proof.go index 14707d2c6e54..cf4d8ae3a38e 100644 --- a/store/iavl/proof.go +++ b/store/iavl/proof.go @@ -8,33 +8,33 @@ import ( "github.com/tendermint/tendermint/crypto/merkle" ) -const ProofOpIAVL = "iavlstore" +const ProofOpIAVLCommitment = "iavlstore" // IavlOP implements merkle.ProofOperator by wrapping an ics23 CommitmentProof // It also contains a Key field to determine which key the proof is proving. // NOTE: CommitmentProof currently can either be ExistenceProof or NonexistenceProof -type IAVLOp struct { +type CommitmentOp struct { Key []byte Proof *ics23.CommitmentProof } -var _ merkle.ProofOperator = IAVLOp{} +var _ merkle.ProofOperator = CommitmentOp{} -func NewIAVLOp(key []byte, proof *ics23.CommitmentProof) IAVLOp { - return IAVLOp{ +func NewCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp { + return CommitmentOp{ Key: key, Proof: proof, } } -// IAVLOpDecoder takes a merkle.ProofOp and attempt to decode it into a IAVLOp ProofOperator -// The proofOp.Data is just a marshalled CommitmentProof. The Key of the IAVLOp is extracted +// CommitmentOpDecoder takes a merkle.ProofOp and attempt to decode it into a CommitmentOp ProofOperator +// The proofOp.Data is just a marshalled CommitmentProof. The Key of the CommitmentOp is extracted // from the unmarshalled proof -func IAVLOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { - if pop.Type != ProofOpIAVL { - return nil, errors.New(fmt.Sprintf("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVL)) +func CommitmentOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { + if pop.Type != ProofOpIAVLCommitment { + return nil, fmt.Errorf("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVLCommitment) } - var op IAVLOp + var op CommitmentOp proof := &ics23.CommitmentProof{} err := proof.Unmarshal(pop.Data) if err != nil { @@ -48,16 +48,16 @@ func IAVLOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { } 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 nil, errors.New("proof type unsupported") } return op, nil } -func (op IAVLOp) GetKey() []byte { +func (op CommitmentOp) GetKey() []byte { return op.Key } -func (op IAVLOp) Run(args [][]byte) ([][]byte, error) { +func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { // Only support an existence proof or nonexistence proof (batch proofs currently unsupported) switch len(args) { case 0: @@ -80,7 +80,7 @@ func (op IAVLOp) Run(args [][]byte) ([][]byte, error) { 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 nil, fmt.Errorf("proof did not verify absence of key: %s", string(op.Key)) } return [][]byte{root}, nil @@ -100,22 +100,22 @@ func (op IAVLOp) Run(args [][]byte) ([][]byte, error) { exists := ics23.VerifyMembership(ics23.IavlSpec, root, op.Proof, op.Key, args[0]) 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 nil, fmt.Errorf("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))) + return nil, fmt.Errorf("args must be length 0 or 1, got: %d", len(args)) } } -func (op IAVLOp) ProofOp() merkle.ProofOp { +func (op CommitmentOp) ProofOp() merkle.ProofOp { bz, err := op.Proof.Marshal() if err != nil { panic(err.Error()) } return merkle.ProofOp{ - Type: ProofOpIAVL, + Type: ProofOpIAVLCommitment, Key: op.Key, Data: bz, } diff --git a/store/iavl/store.go b/store/iavl/store.go index 3b9a2bbaf71a..b7cab2958963 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -307,7 +307,7 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { panic(fmt.Sprintf("unexpected empty absence proof: %s", err.Error())) } } - op := NewIAVLOp(req.Data, commitmentProof) + op := NewCommitmentOp(req.Data, commitmentProof) res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{op.ProofOp()}} case "/subspace": var KVs []types.KVPair From aad1ac71ad9ecc004a6a4189b5c57d5ee1a95524 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 2 Jun 2020 18:30:57 -0400 Subject: [PATCH 04/13] Register commitment op correctly --- store/rootmulti/proof.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/rootmulti/proof.go b/store/rootmulti/proof.go index 80915b587ebb..61a9062017c8 100644 --- a/store/rootmulti/proof.go +++ b/store/rootmulti/proof.go @@ -129,7 +129,7 @@ func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) { func DefaultProofRuntime() (prt *merkle.ProofRuntime) { prt = merkle.NewProofRuntime() prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder) - prt.RegisterOpDecoder(iavlstore.ProofOpIAVL, iavlstore.IAVLOpDecoder) + prt.RegisterOpDecoder(iavlstore.ProofOpIAVLCommitment, iavlstore.CommitmentOpDecoder) prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder) return } From 09519aa99a37531123e06e907f1204189fd05b41 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 3 Jun 2020 19:18:20 +0200 Subject: [PATCH 05/13] Make CommitmentOp generic over all ics23 specs (#6331) * Make the CommitmentOp generic over all ics23 ProofSpecs, using Type to distinguish * Register SimpleMerkle ics23 proof op as well * Addressed linter issues --- store/iavl/proof.go | 56 +++++++++++++++++++++++++++------------- store/iavl/store.go | 2 +- store/rootmulti/proof.go | 2 ++ 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/store/iavl/proof.go b/store/iavl/proof.go index cf4d8ae3a38e..2c97d1de325a 100644 --- a/store/iavl/proof.go +++ b/store/iavl/proof.go @@ -8,20 +8,37 @@ import ( "github.com/tendermint/tendermint/crypto/merkle" ) -const ProofOpIAVLCommitment = "iavlstore" +const ProofOpIAVLCommitment = "ics23:iavl" +const ProofOpSimpleMerkleCommitment = "ics23:simple" -// IavlOP implements merkle.ProofOperator by wrapping an ics23 CommitmentProof +// CommitmentOp implements merkle.ProofOperator by wrapping an ics23 CommitmentProof // It also contains a Key field to determine which key the proof is proving. // NOTE: CommitmentProof currently can either be ExistenceProof or NonexistenceProof +// +// Type and Spec are classified by the kind of merkle proof it represents allowing +// the code to be reused by more types. Spec is never on the wire, but mapped from type in the code. type CommitmentOp struct { + Type string + Spec *ics23.ProofSpec Key []byte Proof *ics23.CommitmentProof } var _ merkle.ProofOperator = CommitmentOp{} -func NewCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp { +func NewIavlCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp { return CommitmentOp{ + Type: ProofOpIAVLCommitment, + Spec: ics23.IavlSpec, + Key: key, + Proof: proof, + } +} + +func NewSimpleMerkleCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp { + return CommitmentOp{ + Type: ProofOpSimpleMerkleCommitment, + Spec: ics23.TendermintSpec, Key: key, Proof: proof, } @@ -31,24 +48,27 @@ func NewCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp { // The proofOp.Data is just a marshalled CommitmentProof. The Key of the CommitmentOp is extracted // from the unmarshalled proof func CommitmentOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { - if pop.Type != ProofOpIAVLCommitment { - return nil, fmt.Errorf("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVLCommitment) + var spec *ics23.ProofSpec + switch pop.Type { + case ProofOpIAVLCommitment: + spec = ics23.IavlSpec + case ProofOpSimpleMerkleCommitment: + spec = ics23.TendermintSpec + default: + return nil, fmt.Errorf("unexpected ProofOp.Type; got %v, want supported ics23 subtype", pop.Type) } - var op CommitmentOp + proof := &ics23.CommitmentProof{} err := proof.Unmarshal(pop.Data) if err != nil { return nil, err } - op.Proof = proof - - // Get Key from proof for now - 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") + + op := CommitmentOp{ + Type: pop.Type, + Key: pop.Key, + Spec: spec, + Proof: proof, } return op, nil } @@ -78,7 +98,7 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { } } - absent := ics23.VerifyNonMembership(ics23.IavlSpec, root, op.Proof, op.Key) + absent := ics23.VerifyNonMembership(op.Spec, root, op.Proof, op.Key) if !absent { return nil, fmt.Errorf("proof did not verify absence of key: %s", string(op.Key)) } @@ -98,7 +118,7 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { return nil, errors.New("could not calculate root from existence proof") } - exists := ics23.VerifyMembership(ics23.IavlSpec, root, op.Proof, op.Key, args[0]) + exists := ics23.VerifyMembership(op.Spec, root, op.Proof, op.Key, args[0]) if !exists { return nil, fmt.Errorf("proof did not verify existence of key %s with given value %x", op.Key, args[0]) } @@ -115,7 +135,7 @@ func (op CommitmentOp) ProofOp() merkle.ProofOp { panic(err.Error()) } return merkle.ProofOp{ - Type: ProofOpIAVLCommitment, + Type: op.Type, Key: op.Key, Data: bz, } diff --git a/store/iavl/store.go b/store/iavl/store.go index b7cab2958963..8921cb306cc7 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -307,7 +307,7 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { panic(fmt.Sprintf("unexpected empty absence proof: %s", err.Error())) } } - op := NewCommitmentOp(req.Data, commitmentProof) + op := NewIavlCommitmentOp(req.Data, commitmentProof) res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{op.ProofOp()}} case "/subspace": var KVs []types.KVPair diff --git a/store/rootmulti/proof.go b/store/rootmulti/proof.go index 61a9062017c8..bdd6a799a3aa 100644 --- a/store/rootmulti/proof.go +++ b/store/rootmulti/proof.go @@ -129,7 +129,9 @@ func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) { func DefaultProofRuntime() (prt *merkle.ProofRuntime) { prt = merkle.NewProofRuntime() prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder) + // they both use the same decoder, which can handle any registered spec for ics23 CommitmentProofs prt.RegisterOpDecoder(iavlstore.ProofOpIAVLCommitment, iavlstore.CommitmentOpDecoder) + prt.RegisterOpDecoder(iavlstore.ProofOpSimpleMerkleCommitment, iavlstore.CommitmentOpDecoder) prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder) return } From 17b6f21078041fc288500114f975dee8e2c5a290 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 3 Jun 2020 17:05:56 -0400 Subject: [PATCH 06/13] move commitment proof to types --- store/iavl/store.go | 2 +- store/{iavl => types}/proof.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename store/{iavl => types}/proof.go (99%) diff --git a/store/iavl/store.go b/store/iavl/store.go index 8921cb306cc7..f6df2310ea98 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -307,7 +307,7 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { panic(fmt.Sprintf("unexpected empty absence proof: %s", err.Error())) } } - op := NewIavlCommitmentOp(req.Data, commitmentProof) + op := types.NewIavlCommitmentOp(req.Data, commitmentProof) res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{op.ProofOp()}} case "/subspace": var KVs []types.KVPair diff --git a/store/iavl/proof.go b/store/types/proof.go similarity index 99% rename from store/iavl/proof.go rename to store/types/proof.go index 2c97d1de325a..16b562d4c57d 100644 --- a/store/iavl/proof.go +++ b/store/types/proof.go @@ -1,4 +1,4 @@ -package iavl +package types import ( "errors" From 056fcfd021326f0bfe6654952ae28ed169e32cc8 Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 3 Jun 2020 19:08:19 -0400 Subject: [PATCH 07/13] Apply suggestions from code review Co-authored-by: colin axner <25233464+colin-axner@users.noreply.github.com> --- store/types/proof.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/types/proof.go b/store/types/proof.go index 16b562d4c57d..0104a3b6dc31 100644 --- a/store/types/proof.go +++ b/store/types/proof.go @@ -44,9 +44,9 @@ func NewSimpleMerkleCommitmentOp(key []byte, proof *ics23.CommitmentProof) Commi } } -// CommitmentOpDecoder takes a merkle.ProofOp and attempt to decode it into a CommitmentOp ProofOperator +// CommitmentOpDecoder takes a merkle.ProofOp and attempts to decode it into a CommitmentOp ProofOperator // The proofOp.Data is just a marshalled CommitmentProof. The Key of the CommitmentOp is extracted -// from the unmarshalled proof +// from the unmarshalled proof. func CommitmentOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { var spec *ics23.ProofSpec switch pop.Type { From 1e13c9f12da6653de63701afc405a55b77142c13 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 3 Jun 2020 19:53:07 -0400 Subject: [PATCH 08/13] address review comments: --- store/iavl/store.go | 11 ++++++--- store/types/errors.go | 11 +++++++++ store/types/proof.go | 54 ++++++++++++++++++++++++++++++------------- 3 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 store/types/errors.go diff --git a/store/iavl/store.go b/store/iavl/store.go index f6df2310ea98..265710d51505 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -282,13 +282,16 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { break } // Continue to prove existence/absence of value - var commitmentProof *ics23.CommitmentProof - var err error + var ( + commitmentProof *ics23.CommitmentProof + err error + ) // Must convert store.Tree to iavl.MutableTree with given version to use in CreateProof iTree, err := tree.GetImmutable(res.Height) if err != nil { - panic("value at height was retrieved successfully without corresponding versioned tree in store") + // sanity check: If value for given version was retrieved, immutable tree must also be retrievable + panic("version exists in store but could not retrieve corresponding versioned tree in store") } mtree := &iavl.MutableTree{ ImmutableTree: iTree, @@ -298,12 +301,14 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { // value was found commitmentProof, err = ics23iavl.CreateMembershipProof(mtree, req.Data) if err != nil { + // sanity check: If value was found, membership proof must be creatable panic(fmt.Sprintf("unexpected value for empty proof: %s", err.Error())) } } else { // value wasn't found commitmentProof, err = ics23iavl.CreateNonMembershipProof(mtree, req.Data) if err != nil { + // sanity check: If value wasn't found, nonmembership proof must be creatable panic(fmt.Sprintf("unexpected empty absence proof: %s", err.Error())) } } diff --git a/store/types/errors.go b/store/types/errors.go new file mode 100644 index 000000000000..780fcdef37ab --- /dev/null +++ b/store/types/errors.go @@ -0,0 +1,11 @@ +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const StoreCodespace = "store" + +var ( + ErrInvalidProof = sdkerrors.Register(StoreCodespace, 2, "invalid proof") +) diff --git a/store/types/proof.go b/store/types/proof.go index 16b562d4c57d..d22a7d34f408 100644 --- a/store/types/proof.go +++ b/store/types/proof.go @@ -1,15 +1,16 @@ package types import ( - "errors" - "fmt" - ics23 "github.com/confio/ics23/go" "github.com/tendermint/tendermint/crypto/merkle" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) -const ProofOpIAVLCommitment = "ics23:iavl" -const ProofOpSimpleMerkleCommitment = "ics23:simple" +const ( + ProofOpIAVLCommitment = "ics23:iavl" + ProofOpSimpleMerkleCommitment = "ics23:simple" +) // CommitmentOp implements merkle.ProofOperator by wrapping an ics23 CommitmentProof // It also contains a Key field to determine which key the proof is proving. @@ -55,7 +56,7 @@ func CommitmentOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { case ProofOpSimpleMerkleCommitment: spec = ics23.TendermintSpec default: - return nil, fmt.Errorf("unexpected ProofOp.Type; got %v, want supported ics23 subtype", pop.Type) + return nil, sdkerrors.Wrapf(ErrInvalidProof, "unexpected ProofOp.Type; got %v, want supported ics23 subtype", pop.Type) } proof := &ics23.CommitmentProof{} @@ -77,6 +78,15 @@ func (op CommitmentOp) GetKey() []byte { return op.Key } +// Run takes in a list of arguments and attempts to run the proof op against these arguments +// Returns the root wrapped in [][]byte if the proof op succeeds with given args. If not, +// it will return an error. +// +// CommitmentOp will accept args of length 1 or length 0 +// If length 1 args is passed in, then CommitmentOp will attempt to prove the existence of the key +// with the value provided by args[0] using the embedded CommitmentProof and return the CommitmentRoot of the proof +// If length 0 args is passed in, then CommitmentOp will attempt to prove the absence of the key +// in the CommitmentOp and return the CommitmentRoot of the proof func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { // Only support an existence proof or nonexistence proof (batch proofs currently unsupported) switch len(args) { @@ -84,23 +94,35 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { // 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") + return nil, sdkerrors.Wrap(ErrInvalidProof, "proof is not a nonexistence proof and args is nil") } // get root from either left or right existence proof. Note they must have the same root if both exist // and at least one proof must be non-nil - root, err := nonexistProof.Nonexist.Left.Calculate() - if err != nil { - // Left proof may be nil, check right proof + var ( + root []byte + err error + ) + // check left proof to calculate root + if nonexistProof.Nonexist.Left != nil { + root, err = nonexistProof.Nonexist.Left.Calculate() + if err != nil { + return nil, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from nonexistence proof") + } + } else if nonexistProof.Nonexist.Right != nil { + // Left proof is nil, check right proof root, err = nonexistProof.Nonexist.Right.Calculate() if err != nil { - return nil, errors.New("could not calculate root from nonexistence proof") + return nil, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from nonexistence proof") } + } else { + // both left and right existence proofs are empty, return error + return nil, sdkerrors.Wrap(ErrInvalidProof, "nonexistence proof is empty") } absent := ics23.VerifyNonMembership(op.Spec, root, op.Proof, op.Key) if !absent { - return nil, fmt.Errorf("proof did not verify absence of key: %s", string(op.Key)) + return nil, sdkerrors.Wrapf(ErrInvalidProof, "proof did not verify absence of key: %s", string(op.Key)) } return [][]byte{root}, nil @@ -109,23 +131,23 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { // 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") + return nil, sdkerrors.Wrap(ErrInvalidProof, "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") + return nil, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from existence proof") } exists := ics23.VerifyMembership(op.Spec, root, op.Proof, op.Key, args[0]) if !exists { - return nil, fmt.Errorf("proof did not verify existence of key %s with given value %x", op.Key, args[0]) + return nil, sdkerrors.Wrapf(ErrInvalidProof, "proof did not verify existence of key %s with given value %x", op.Key, args[0]) } return [][]byte{root}, nil default: - return nil, fmt.Errorf("args must be length 0 or 1, got: %d", len(args)) + return nil, sdkerrors.Wrapf(ErrInvalidProof, "args must be length 0 or 1, got: %d", len(args)) } } From 28c344c5d57fb414772138bdc8b8326142702716 Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 4 Jun 2020 12:02:13 -0400 Subject: [PATCH 09/13] Apply suggestions from code review Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- store/iavl/store.go | 2 +- store/types/proof.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/store/iavl/store.go b/store/iavl/store.go index 265710d51505..8f1b9b36457b 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -291,7 +291,7 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { iTree, err := tree.GetImmutable(res.Height) if err != nil { // sanity check: If value for given version was retrieved, immutable tree must also be retrievable - panic("version exists in store but could not retrieve corresponding versioned tree in store") + panic(fmt.Sprintf("version exists in store but could not retrieve corresponding versioned tree in store, %s", err.Error())) } mtree := &iavl.MutableTree{ ImmutableTree: iTree, diff --git a/store/types/proof.go b/store/types/proof.go index 56f029c11f1d..eefd9a400b26 100644 --- a/store/types/proof.go +++ b/store/types/proof.go @@ -56,7 +56,7 @@ func CommitmentOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { case ProofOpSimpleMerkleCommitment: spec = ics23.TendermintSpec default: - return nil, sdkerrors.Wrapf(ErrInvalidProof, "unexpected ProofOp.Type; got %v, want supported ics23 subtype", pop.Type) + return nil, sdkerrors.Wrapf(ErrInvalidProof, "unexpected ProofOp.Type; got %s, want supported ics23 subtypes 'ProofOpIAVLCommitment' or 'ProofOpSimpleMerkleCommitment'", pop.Type) } proof := &ics23.CommitmentProof{} From cf6a06179dec8517f401f79d007fa27b322248fd Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 4 Jun 2020 13:22:59 -0400 Subject: [PATCH 10/13] allow proofs against empty store --- store/iavl/store.go | 2 +- store/rootmulti/proof_test.go | 30 ++++++++++++++++++++++++++++++ store/types/proof.go | 5 +++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/store/iavl/store.go b/store/iavl/store.go index 265710d51505..ec722c348143 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -309,7 +309,7 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { commitmentProof, err = ics23iavl.CreateNonMembershipProof(mtree, req.Data) if err != nil { // sanity check: If value wasn't found, nonmembership proof must be creatable - panic(fmt.Sprintf("unexpected empty absence proof: %s", err.Error())) + panic(fmt.Sprintf("unexpected error for nonexistence proof: %s", err.Error())) } } op := types.NewIavlCommitmentOp(req.Data, commitmentProof) diff --git a/store/rootmulti/proof_test.go b/store/rootmulti/proof_test.go index edb592a11519..d78886a88f22 100644 --- a/store/rootmulti/proof_test.go +++ b/store/rootmulti/proof_test.go @@ -109,6 +109,36 @@ func TestVerifyMultiStoreQueryProof(t *testing.T) { require.NotNil(t, err) } +func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewStore(db) + iavlStoreKey := types.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, types.StoreTypeIAVL, nil) + err := store.LoadVersion(0) + require.NoError(t, err) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err = prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) +} + func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { // Create main tree for testing. db := dbm.NewMemDB() diff --git a/store/types/proof.go b/store/types/proof.go index 56f029c11f1d..83dccdb4e083 100644 --- a/store/types/proof.go +++ b/store/types/proof.go @@ -116,8 +116,9 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { return nil, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from nonexistence proof") } } else { - // both left and right existence proofs are empty, return error - return nil, sdkerrors.Wrap(ErrInvalidProof, "nonexistence proof is empty") + // both left and right existence proofs are empty + // this only proves absence against a nil root (empty store) + return [][]byte{nil}, nil } absent := ics23.VerifyNonMembership(op.Spec, root, op.Proof, op.Key) From c715058079c4d15d584765689407460dfce4c962 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 4 Jun 2020 13:44:46 -0400 Subject: [PATCH 11/13] address review comments --- store/iavl/store.go | 54 ++++++++++++++++++++++++++------------------ store/types/proof.go | 10 +++++--- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/store/iavl/store.go b/store/iavl/store.go index d8b4262cb352..0548b3d01696 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -281,12 +281,8 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { if !req.Prove { break } - // Continue to prove existence/absence of value - var ( - commitmentProof *ics23.CommitmentProof - err error - ) + // Continue to prove existence/absence of value // Must convert store.Tree to iavl.MutableTree with given version to use in CreateProof iTree, err := tree.GetImmutable(res.Height) if err != nil { @@ -297,23 +293,9 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { ImmutableTree: iTree, } - if res.Value != nil { - // value was found - commitmentProof, err = ics23iavl.CreateMembershipProof(mtree, req.Data) - if err != nil { - // sanity check: If value was found, membership proof must be creatable - panic(fmt.Sprintf("unexpected value for empty proof: %s", err.Error())) - } - } else { - // value wasn't found - commitmentProof, err = ics23iavl.CreateNonMembershipProof(mtree, req.Data) - if err != nil { - // sanity check: If value wasn't found, nonmembership proof must be creatable - panic(fmt.Sprintf("unexpected error for nonexistence proof: %s", err.Error())) - } - } - op := types.NewIavlCommitmentOp(req.Data, commitmentProof) - res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{op.ProofOp()}} + // get proof from tree and convert to merkle.Proof before adding to result + res.Proof = getProofFromTree(mtree, req.Data, res.Value != nil) + case "/subspace": var KVs []types.KVPair @@ -335,6 +317,34 @@ func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { return res } +// Takes a MutableTree, a key, and a flag for creating existence or absence proof and returns the +// appropriate merkle.Proof. Since this must be called after querying for the value, this function should never error +// Thus, it will panic on error rather than returning it +func getProofFromTree(tree *iavl.MutableTree, key []byte, exists bool) *merkle.Proof { + var ( + commitmentProof *ics23.CommitmentProof + err error + ) + + if exists { + // value was found + commitmentProof, err = ics23iavl.CreateMembershipProof(tree, key) + if err != nil { + // sanity check: If value was found, membership proof must be creatable + panic(fmt.Sprintf("unexpected value for empty proof: %s", err.Error())) + } + } else { + // value wasn't found + commitmentProof, err = ics23iavl.CreateNonMembershipProof(tree, key) + if err != nil { + // sanity check: If value wasn't found, nonmembership proof must be creatable + panic(fmt.Sprintf("unexpected error for nonexistence proof: %s", err.Error())) + } + } + op := types.NewIavlCommitmentOp(key, commitmentProof) + return &merkle.Proof{Ops: []merkle.ProofOp{op.ProofOp()}} +} + //---------------------------------------- // Implements types.Iterator. diff --git a/store/types/proof.go b/store/types/proof.go index 060660ab503f..f78a337e25af 100644 --- a/store/types/proof.go +++ b/store/types/proof.go @@ -103,19 +103,20 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { root []byte err error ) + switch { // check left proof to calculate root - if nonexistProof.Nonexist.Left != nil { + case nonexistProof.Nonexist.Left != nil: root, err = nonexistProof.Nonexist.Left.Calculate() if err != nil { return nil, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from nonexistence proof") } - } else if nonexistProof.Nonexist.Right != nil { + case nonexistProof.Nonexist.Right != nil: // Left proof is nil, check right proof root, err = nonexistProof.Nonexist.Right.Calculate() if err != nil { return nil, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from nonexistence proof") } - } else { + default: // both left and right existence proofs are empty // this only proves absence against a nil root (empty store) return [][]byte{nil}, nil @@ -152,6 +153,9 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { } } +// ProofOp implements ProofOperator interface and converts a CommitmentOp +// into a merkle.ProofOp format that can later be decoded by CommitmentOpDecoder +// back into a CommitmentOp for proof verification func (op CommitmentOp) ProofOp() merkle.ProofOp { bz, err := op.Proof.Marshal() if err != nil { From f59df0044e48273d6f8a0cf245b3739bc4232e2a Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 4 Jun 2020 14:33:43 -0400 Subject: [PATCH 12/13] Update store/types/proof.go Co-authored-by: Alexander Bezobchuk --- store/types/proof.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/store/types/proof.go b/store/types/proof.go index f78a337e25af..0d38889d0c9f 100644 --- a/store/types/proof.go +++ b/store/types/proof.go @@ -142,8 +142,7 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { return nil, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from existence proof") } - exists := ics23.VerifyMembership(op.Spec, root, op.Proof, op.Key, args[0]) - if !exists { + if !ics23.VerifyMembership(op.Spec, root, op.Proof, op.Key, args[0]) { return nil, sdkerrors.Wrapf(ErrInvalidProof, "proof did not verify existence of key %s with given value %x", op.Key, args[0]) } From 2a471ecfa7ff21f4526a69a7a30f898c88eb77d1 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 4 Jun 2020 16:53:32 -0400 Subject: [PATCH 13/13] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08cd61e2c234..c8407389246d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,6 +148,7 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa * (x/capability) [\#5828](https://github.com/cosmos/cosmos-sdk/pull/5828) Capability module integration as outlined in [ADR 3 - Dynamic Capability Store](https://github.com/cosmos/tree/master/docs/architecture/adr-003-dynamic-capability-store.md). * (x/params) [\#6005](https://github.com/cosmos/cosmos-sdk/pull/6005) Add new CLI command for querying raw x/params parameters by subspace and key. * (x/ibc) [\#5769](https://github.com/cosmos/cosmos-sdk/pull/5769) [ICS 009 - Loopback Client](https://github.com/cosmos/ics/tree/master/spec/ics-009-loopback-client) subpackage +* (store) [\#6324](https://github.com/cosmos/cosmos-sdk/pull/6324) IAVL store query proofs now return CommitmentOp which wraps an ics23 CommitmentProof ### Bug Fixes