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

Add Proper query to BaseApp and rootMultiStore #404

Merged
merged 9 commits into from
Feb 7, 2018
9 changes: 7 additions & 2 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,14 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
}

// Implements ABCI.
// Delegates to CommitMultiStore if it implements Queryable
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
// TODO: See app/query.go
return
queryable, ok := app.cms.(sdk.Queryable)
if !ok {
msg := "application doesn't support queries"
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
}
return queryable.Query(req)
}

// Implements ABCI.
Expand Down
12 changes: 8 additions & 4 deletions store/cachemultistore.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import (
// cacheMultiStore holds many cache-wrapped stores.
// Implements MultiStore.
type cacheMultiStore struct {
db CacheKVStore
stores map[StoreKey]CacheWrap
db CacheKVStore
stores map[StoreKey]CacheWrap
keysByName map[string]StoreKey
}

var _ CacheMultiStore = cacheMultiStore{}

func newCacheMultiStoreFromRMS(rms *rootMultiStore) cacheMultiStore {
cms := cacheMultiStore{
db: NewCacheKVStore(dbStoreAdapter{rms.db}),
stores: make(map[StoreKey]CacheWrap, len(rms.stores)),
db: NewCacheKVStore(dbStoreAdapter{rms.db}),
stores: make(map[StoreKey]CacheWrap, len(rms.stores)),
keysByName: rms.keysByName,
}
for key, store := range rms.stores {
cms.stores[key] = store.CacheWrap()
Expand Down
51 changes: 51 additions & 0 deletions store/iavlstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"sync"

abci "github.com/tendermint/abci/types"
"github.com/tendermint/iavl"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
Expand Down Expand Up @@ -31,6 +32,7 @@ func LoadIAVLStore(db dbm.DB, id CommitID) (CommitStore, error) {

var _ KVStore = (*iavlStore)(nil)
var _ CommitStore = (*iavlStore)(nil)
var _ Queryable = (*iavlStore)(nil)

// iavlStore Implements KVStore and CommitStore.
type iavlStore struct {
Expand Down Expand Up @@ -123,6 +125,55 @@ func (st *iavlStore) ReverseIterator(start, end []byte) Iterator {
return newIAVLIterator(st.tree.Tree(), start, end, false)
}

// Query implements ABCI interface, allows queries
//
// by default we will return from (latest height -1),
// as we will have merkle proofs immediately (header height = data height + 1)
// If latest-1 is not present, use latest (which must be present)
// if you care to have the latest data to see a tx results, you must
// explicitly set the height you want to see
func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
if len(req.Data) == 0 {
msg := "Query cannot be zero length"
return sdk.ErrTxParse(msg).Result().ToQuery()
}

tree := st.tree
height := req.Height
if height == 0 {
latest := tree.Version64()
if tree.VersionExists(latest - 1) {
height = latest - 1
} else {
height = latest
}
}
// store the height we chose in the response
res.Height = height

switch req.Path {
case "/store", "/key": // Get by key
key := req.Data // Data holds the key bytes
res.Key = key
if req.Prove {
value, proof, err := tree.GetVersionedWithProof(key, height)
if err != nil {
res.Log = err.Error()
break
}
res.Value = value
res.Proof = proof.Bytes()
} else {
_, res.Value = tree.GetVersioned(key, height)
}

default:
msg := fmt.Sprintf("Unexpected Query path: %v", req.Path)
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
}
return
}

//----------------------------------------

// Implements Iterator.
Expand Down
64 changes: 63 additions & 1 deletion store/iavlstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (

"github.com/stretchr/testify/assert"

abci "github.com/tendermint/abci/types"
"github.com/tendermint/iavl"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"

"github.com/tendermint/iavl"
sdk "github.com/cosmos/cosmos-sdk/types"
)

var (
Expand Down Expand Up @@ -79,3 +81,63 @@ func TestIAVLIterator(t *testing.T) {
i += 1
}
}

func TestIAVLStoreQuery(t *testing.T) {
db := dbm.NewMemDB()
tree := iavl.NewVersionedTree(db, cacheSize)
iavlStore := newIAVLStore(tree, numHistory)

k, v := []byte("wind"), []byte("blows")
k2, v2 := []byte("water"), []byte("flows")
v3 := []byte("is cold")
// k3, v3 := []byte("earth"), []byte("soes")
// k4, v4 := []byte("fire"), []byte("woes")

cid := iavlStore.Commit()
ver := cid.Version
query := abci.RequestQuery{Path: "/key", Data: k, Height: ver}

// set data without commit, doesn't show up
iavlStore.Set(k, v)
qres := iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Nil(t, qres.Value)

// commit it, but still don't see on old version
cid = iavlStore.Commit()
qres = iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Nil(t, qres.Value)

// but yes on the new version
query.Height = cid.Version
qres = iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v, qres.Value)

// modify
iavlStore.Set(k2, v2)
iavlStore.Set(k, v3)
cid = iavlStore.Commit()

// query will return old values, as height is fixed
qres = iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v, qres.Value)

// update to latest in the query and we are happy
query.Height = cid.Version
qres = iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v3, qres.Value)
query2 := abci.RequestQuery{Path: "/key", Data: k2, Height: cid.Version}
qres = iavlStore.Query(query2)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v2, qres.Value)

// default (height 0) will show latest -1
query0 := abci.RequestQuery{Path: "/store", Data: k}
qres = iavlStore.Query(query0)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v, qres.Value)
}
69 changes: 68 additions & 1 deletion store/rootmultistore.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package store

import (
"fmt"
"strings"

"golang.org/x/crypto/ripemd160"

Copy link
Contributor

Choose a reason for hiding this comment

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

No line between external imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

https://github.com/tendermint/coding/blob/master/go/coding_standard.md#importing-libraries

In some cases, it's nice to separate into three: standard lib, external libs, tendermint libs.

abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/merkle"
"golang.org/x/crypto/ripemd160"

sdk "github.com/cosmos/cosmos-sdk/types"
)
Expand All @@ -24,15 +27,18 @@ type rootMultiStore struct {
lastCommitID CommitID
storesParams map[StoreKey]storeParams
stores map[StoreKey]CommitStore
keysByName map[string]StoreKey
}

var _ CommitMultiStore = (*rootMultiStore)(nil)
var _ Queryable = (*rootMultiStore)(nil)

func NewCommitMultiStore(db dbm.DB) *rootMultiStore {
return &rootMultiStore{
db: db,
storesParams: make(map[StoreKey]storeParams),
stores: make(map[StoreKey]CommitStore),
keysByName: make(map[string]StoreKey),
}
}

Expand All @@ -53,6 +59,7 @@ func (rs *rootMultiStore) MountStoreWithDB(key StoreKey, typ StoreType, db dbm.D
db: db,
typ: typ,
}
rs.keysByName[key.Name()] = key
}

// Implements CommitMultiStore.
Expand Down Expand Up @@ -169,6 +176,66 @@ func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore {
return rs.stores[key].(KVStore)
}

// getStoreByName will first convert the original name to
// a special key, before looking up the CommitStore.
// This is not exposed to the extensions (which will need the
// StoreKey), but is useful in main, and particularly app.Query,
// in order to convert human strings into CommitStores.
func (rs *rootMultiStore) getStoreByName(name string) Store {
key := rs.keysByName[name]
if key == nil {
return nil
}
return rs.stores[key]
}

//---------------------- Query ------------------

// Query calls substore.Query with the same `req` where `req.Path` is
// modified to remove the substore prefix.
// Ie. `req.Path` here is `/<substore>/<path>`, and trimmed to `/<path>` for the substore.
// TODO: add proof for `multistore -> substore`.
func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery {
// Query just routes this to a substore.
path := req.Path
storeName, subpath, err := parsePath(path)
if err != nil {
return err.Result().ToQuery()
}

store := rs.getStoreByName(storeName)
if store == nil {
msg := fmt.Sprintf("no such store: %s", storeName)
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
}
queryable, ok := store.(Queryable)
if !ok {
msg := fmt.Sprintf("store %s doesn't support queries", storeName)
return sdk.ErrUnknownRequest(msg).Result().ToQuery()
}

// trim the path and make the query
req.Path = subpath
res := queryable.Query(req)
return res
}

// parsePath expects a format like /<storeName>[/<subpath>]
// Must start with /, subpath may be empty
// Returns error if it doesn't start with /
func parsePath(path string) (storeName string, subpath string, err sdk.Error) {
if !strings.HasPrefix(path, "/") {
err = sdk.ErrUnknownRequest(fmt.Sprintf("invalid path: %s", path))
return
}
paths := strings.SplitN(path[1:], "/", 2)
storeName = paths[0]
if len(paths) == 2 {
subpath = "/" + paths[1]
}
return
}

//----------------------------------------

func (rs *rootMultiStore) loadCommitStoreFromParams(id CommitID, params storeParams) (store CommitStore, err error) {
Expand Down
Loading