Switch IAVL Store query to use ics proofs (#6324)

* switch iavl store to use ics proof

* fix proofs to return for correct height

* appease linter

* Register commitment op correctly

* 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

* move commitment proof to types

* Apply suggestions from code review

Co-authored-by: colin axner <25233464+colin-axner@users.noreply.github.com>

* address review comments:

* Apply suggestions from code review

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* allow proofs against empty store

* address review comments

* Update store/types/proof.go

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>

* add changelog

Co-authored-by: Ethan Frey <ethanfrey@users.noreply.github.com>
Co-authored-by: colin axner <25233464+colin-axner@users.noreply.github.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Marko <marbar3778@yahoo.com>
This commit is contained in:
Aditya
2020-06-08 15:02:25 -04:00
committed by GitHub
co-authored by colin axner Federico Kunze Alexander Bezobchuk Ethan Frey Marko
parent 65ea305336
commit d9e1497e89
7 changed files with 241 additions and 26 deletions
+47 -23
View File
@@ -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,25 @@ 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")
}
}
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()}}
}
} else {
_, res.Value = tree.GetVersioned(key, res.Height)
_, res.Value = tree.GetVersioned(key, res.Height)
if !req.Prove {
break
}
// 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 {
// sanity check: If value for given version was retrieved, immutable tree must also be retrievable
panic(fmt.Sprintf("version exists in store but could not retrieve corresponding versioned tree in store, %s", err.Error()))
}
mtree := &iavl.MutableTree{
ImmutableTree: iTree,
}
// 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
@@ -321,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.
+4 -3
View File
@@ -1,8 +1,9 @@
package rootmulti
import (
"github.com/tendermint/iavl"
"github.com/tendermint/tendermint/crypto/merkle"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
)
// RequireProof returns whether proof is required for the subpath.
@@ -21,7 +22,7 @@ func RequireProof(subpath string) bool {
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(storetypes.ProofOpIAVLCommitment, storetypes.CommitmentOpDecoder)
prt.RegisterOpDecoder(storetypes.ProofOpSimpleMerkleCommitment, storetypes.CommitmentOpDecoder)
return
}
+11
View File
@@ -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")
)
+168
View File
@@ -0,0 +1,168 @@
package types
import (
ics23 "github.com/confio/ics23/go"
"github.com/tendermint/tendermint/crypto/merkle"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
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.
// 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 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,
}
}
// 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.
func CommitmentOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
var spec *ics23.ProofSpec
switch pop.Type {
case ProofOpIAVLCommitment:
spec = ics23.IavlSpec
case ProofOpSimpleMerkleCommitment:
spec = ics23.TendermintSpec
default:
return nil, sdkerrors.Wrapf(ErrInvalidProof, "unexpected ProofOp.Type; got %s, want supported ics23 subtypes 'ProofOpIAVLCommitment' or 'ProofOpSimpleMerkleCommitment'", pop.Type)
}
proof := &ics23.CommitmentProof{}
err := proof.Unmarshal(pop.Data)
if err != nil {
return nil, err
}
op := CommitmentOp{
Type: pop.Type,
Key: pop.Key,
Spec: spec,
Proof: proof,
}
return op, nil
}
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) {
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, 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
var (
root []byte
err error
)
switch {
// check left proof to calculate root
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")
}
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")
}
default:
// 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)
if !absent {
return nil, sdkerrors.Wrapf(ErrInvalidProof, "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, 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, sdkerrors.Wrap(ErrInvalidProof, "could not calculate root from existence proof")
}
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])
}
return [][]byte{root}, nil
default:
return nil, sdkerrors.Wrapf(ErrInvalidProof, "args must be length 0 or 1, got: %d", len(args))
}
}
// 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 {
panic(err.Error())
}
return merkle.ProofOp{
Type: op.Type,
Key: op.Key,
Data: bz,
}
}