diff --git a/go.mod b/go.mod index 3b3b2c17b5c2..8910307121ac 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/99designs/keyring v1.1.5 github.com/bgentry/speakeasy v0.1.0 github.com/btcsuite/btcd v0.20.1-beta + 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 diff --git a/go.sum b/go.sum index 45716244022b..c8c6894180e2 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,9 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX 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= @@ -451,6 +454,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= @@ -458,6 +462,7 @@ github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICf github.com/tendermint/tendermint v0.33.4 h1:NM3G9618yC5PaaxGrcAySc5ylc1PAANeIx42u2Re/jo= github.com/tendermint/tendermint v0.33.4/go.mod h1:6NW9DVkvsvqmCY6wbRsOo66qGDhMXglRL70aXajvBEA= 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..dbf02c4fd1b9 --- /dev/null +++ b/store/iavl/proof.go @@ -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) { + 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) { + 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") + } + + root, err := nonexistProof.Nonexist.Left.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