Store Refactor 1 (#2985)

This commit is contained in:
Joon
2019-02-01 17:03:09 -08:00
committed by Jack Zampolin
parent 45d59b0792
commit 08e62fb157
58 changed files with 1758 additions and 1611 deletions
+33
View File
@@ -0,0 +1,33 @@
package rootmulti
import (
"github.com/cosmos/cosmos-sdk/store/dbadapter"
"github.com/cosmos/cosmos-sdk/store/types"
)
var commithash = []byte("FAKE_HASH")
//----------------------------------------
// commitDBStoreWrapper should only be used for simulation/debugging,
// as it doesn't compute any commit hash, and it cannot load older state.
// Wrapper type for dbm.Db with implementation of KVStore
type commitDBStoreAdapter struct {
dbadapter.Store
}
func (cdsa commitDBStoreAdapter) Commit() types.CommitID {
return types.CommitID{
Version: -1,
Hash: commithash,
}
}
func (cdsa commitDBStoreAdapter) LastCommitID() types.CommitID {
return types.CommitID{
Version: -1,
Hash: commithash,
}
}
func (cdsa commitDBStoreAdapter) SetPruning(_ types.PruningOptions) {}
+140
View File
@@ -0,0 +1,140 @@
package rootmulti
import (
"bytes"
"fmt"
"github.com/tendermint/iavl"
"github.com/tendermint/tendermint/crypto/merkle"
cmn "github.com/tendermint/tendermint/libs/common"
)
// MultiStoreProof defines a collection of store proofs in a multi-store
type MultiStoreProof struct {
StoreInfos []storeInfo
}
func NewMultiStoreProof(storeInfos []storeInfo) *MultiStoreProof {
return &MultiStoreProof{StoreInfos: storeInfos}
}
// ComputeRootHash returns the root hash for a given multi-store proof.
func (proof *MultiStoreProof) ComputeRootHash() []byte {
ci := commitInfo{
Version: -1, // TODO: Not needed; improve code.
StoreInfos: proof.StoreInfos,
}
return ci.Hash()
}
// RequireProof returns whether proof is required for the subpath.
func RequireProof(subpath string) bool {
// XXX: create a better convention.
// Currently, only when query subpath is "/key", will proof be included in
// response. If there are some changes about proof building in iavlstore.go,
// we must change code here to keep consistency with iavlStore#Query.
if subpath == "/key" {
return true
}
return false
}
//-----------------------------------------------------------------------------
var _ merkle.ProofOperator = MultiStoreProofOp{}
// the multi-store proof operation constant value
const ProofOpMultiStore = "multistore"
// TODO: document
type MultiStoreProofOp struct {
// Encoded in ProofOp.Key
key []byte
// To encode in ProofOp.Data.
Proof *MultiStoreProof `json:"proof"`
}
func NewMultiStoreProofOp(key []byte, proof *MultiStoreProof) MultiStoreProofOp {
return MultiStoreProofOp{
key: key,
Proof: proof,
}
}
// MultiStoreProofOpDecoder returns a multi-store merkle proof operator from a
// given proof operation.
func MultiStoreProofOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
if pop.Type != ProofOpMultiStore {
return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpMultiStore)
}
// XXX: a bit strange as we'll discard this, but it works
var op MultiStoreProofOp
err := cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op)
if err != nil {
return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into MultiStoreProofOp")
}
return NewMultiStoreProofOp(pop.Key, op.Proof), nil
}
// ProofOp return a merkle proof operation from a given multi-store proof
// operation.
func (op MultiStoreProofOp) ProofOp() merkle.ProofOp {
bz := cdc.MustMarshalBinaryLengthPrefixed(op)
return merkle.ProofOp{
Type: ProofOpMultiStore,
Key: op.key,
Data: bz,
}
}
// String implements the Stringer interface for a mult-store proof operation.
func (op MultiStoreProofOp) String() string {
return fmt.Sprintf("MultiStoreProofOp{%v}", op.GetKey())
}
// GetKey returns the key for a multi-store proof operation.
func (op MultiStoreProofOp) GetKey() []byte {
return op.key
}
// Run executes a multi-store proof operation for a given value. It returns
// the root hash if the value matches all the store's commitID's hash or an
// error otherwise.
func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) {
if len(args) != 1 {
return nil, cmn.NewError("Value size is not 1")
}
value := args[0]
root := op.Proof.ComputeRootHash()
for _, si := range op.Proof.StoreInfos {
if si.Name == string(op.key) {
if bytes.Equal(value, si.Core.CommitID.Hash) {
return [][]byte{root}, nil
}
return nil, cmn.NewError("hash mismatch for substore %v: %X vs %X", si.Name, si.Core.CommitID.Hash, value)
}
}
return nil, cmn.NewError("key %v not found in multistore proof", op.key)
}
//-----------------------------------------------------------------------------
// XXX: This should be managed by the rootMultiStore which may want to register
// more proof ops?
func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder)
prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder)
return
}
+177
View File
@@ -0,0 +1,177 @@
package rootmulti
import (
"testing"
"github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/store/iavl"
stypes "github.com/cosmos/cosmos-sdk/store/types"
)
func TestVerifyIAVLStoreQueryProof(t *testing.T) {
// Create main tree for testing.
db := dbm.NewMemDB()
iStore, err := iavl.LoadStore(db, types.CommitID{}, stypes.PruneNothing)
store := iStore.(*iavl.Store)
require.Nil(t, err)
store.Set([]byte("MYKEY"), []byte("MYVALUE"))
cid := store.Commit()
// Get Proof
res := store.Query(abci.RequestQuery{
Path: "/key", // required path to get key/value+proof
Data: []byte("MYKEY"),
Prove: true,
})
require.NotNil(t, res.Proof)
// Verify proof.
prt := DefaultProofRuntime()
err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE"))
require.Nil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY_NOT", []byte("MYVALUE"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY/MYKEY", []byte("MYVALUE"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "MYKEY", []byte("MYVALUE"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE_NOT"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte(nil))
require.NotNil(t, err)
}
func TestVerifyMultiStoreQueryProof(t *testing.T) {
// Create main tree for testing.
db := dbm.NewMemDB()
store := NewStore(db)
iavlStoreKey := types.NewKVStoreKey("iavlStoreKey")
store.MountStoreWithDB(iavlStoreKey, types.StoreTypeIAVL, nil)
store.LoadVersion(0)
iavlStore := store.GetCommitStore(iavlStoreKey).(*iavl.Store)
iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE"))
cid := store.Commit()
// 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.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE"))
require.Nil(t, err)
// Verify proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE"))
require.Nil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY_NOT", []byte("MYVALUE"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY/MYKEY", []byte("MYVALUE"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "iavlStoreKey/MYKEY", []byte("MYVALUE"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE_NOT"))
require.NotNil(t, err)
// Verify (bad) proof.
err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte(nil))
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)
store.LoadVersion(0)
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()
store := NewStore(db)
iavlStoreKey := types.NewKVStoreKey("iavlStoreKey")
store.MountStoreWithDB(iavlStoreKey, types.StoreTypeIAVL, nil)
store.LoadVersion(0)
iavlStore := store.GetCommitStore(iavlStoreKey).(*iavl.Store)
iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE"))
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("MYABSENTKEY"),
Prove: true,
})
require.NotNil(t, res.Proof)
// Verify proof.
prt := DefaultProofRuntime()
err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY")
require.Nil(t, err)
// Verify (bad) proof.
prt = DefaultProofRuntime()
err = prt.VerifyAbsence(res.Proof, cid.Hash, "/MYABSENTKEY")
require.NotNil(t, err)
// Verify (bad) proof.
prt = DefaultProofRuntime()
err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY", []byte(""))
require.NotNil(t, err)
}
+537
View File
@@ -0,0 +1,537 @@
package rootmulti
import (
"fmt"
"io"
"strings"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/crypto/tmhash"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/store/cachemulti"
"github.com/cosmos/cosmos-sdk/store/dbadapter"
"github.com/cosmos/cosmos-sdk/store/iavl"
"github.com/cosmos/cosmos-sdk/store/tracekv"
"github.com/cosmos/cosmos-sdk/store/transient"
"github.com/cosmos/cosmos-sdk/types"
)
const (
latestVersionKey = "s/latest"
commitInfoKeyFmt = "s/%d" // s/<version>
)
// Store is composed of many CommitStores. Name contrasts with
// cacheMultiStore which is for cache-wrapping other MultiStores. It implements
// the CommitMultiStore interface.
type Store struct {
db dbm.DB
lastCommitID types.CommitID
pruningOpts types.PruningOptions
storesParams map[types.StoreKey]storeParams
stores map[types.StoreKey]types.CommitStore
keysByName map[string]types.StoreKey
traceWriter io.Writer
traceContext types.TraceContext
}
var _ types.CommitMultiStore = (*Store)(nil)
var _ types.Queryable = (*Store)(nil)
// nolint
func NewStore(db dbm.DB) *Store {
return &Store{
db: db,
storesParams: make(map[types.StoreKey]storeParams),
stores: make(map[types.StoreKey]types.CommitStore),
keysByName: make(map[string]types.StoreKey),
}
}
// Implements CommitMultiStore
func (rs *Store) SetPruning(pruningOpts types.PruningOptions) {
rs.pruningOpts = pruningOpts
for _, substore := range rs.stores {
substore.SetPruning(pruningOpts)
}
}
// Implements Store.
func (rs *Store) GetStoreType() types.StoreType {
return types.StoreTypeMulti
}
// Implements CommitMultiStore.
func (rs *Store) MountStoreWithDB(key types.StoreKey, typ types.StoreType, db dbm.DB) {
if key == nil {
panic("MountIAVLStore() key cannot be nil")
}
if _, ok := rs.storesParams[key]; ok {
panic(fmt.Sprintf("Store duplicate store key %v", key))
}
if _, ok := rs.keysByName[key.Name()]; ok {
panic(fmt.Sprintf("Store duplicate store key name %v", key))
}
rs.storesParams[key] = storeParams{
key: key,
typ: typ,
db: db,
}
rs.keysByName[key.Name()] = key
}
// Implements CommitMultiStore.
func (rs *Store) GetCommitStore(key types.StoreKey) types.CommitStore {
return rs.stores[key]
}
// Implements CommitMultiStore.
func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore {
return rs.stores[key].(types.CommitKVStore)
}
// Implements CommitMultiStore.
func (rs *Store) LoadLatestVersion() error {
ver := getLatestVersion(rs.db)
return rs.LoadVersion(ver)
}
// Implements CommitMultiStore.
func (rs *Store) LoadVersion(ver int64) error {
// Special logic for version 0
if ver == 0 {
for key, storeParams := range rs.storesParams {
id := types.CommitID{}
store, err := rs.loadCommitStoreFromParams(key, id, storeParams)
if err != nil {
return fmt.Errorf("failed to load Store: %v", err)
}
rs.stores[key] = store
}
rs.lastCommitID = types.CommitID{}
return nil
}
// Otherwise, version is 1 or greater
// Get commitInfo
cInfo, err := getCommitInfo(rs.db, ver)
if err != nil {
return err
}
// Convert StoreInfos slice to map
infos := make(map[types.StoreKey]storeInfo)
for _, storeInfo := range cInfo.StoreInfos {
infos[rs.nameToKey(storeInfo.Name)] = storeInfo
}
// Load each Store
var newStores = make(map[types.StoreKey]types.CommitStore)
for key, storeParams := range rs.storesParams {
var id types.CommitID
info, ok := infos[key]
if ok {
id = info.Core.CommitID
}
store, err := rs.loadCommitStoreFromParams(key, id, storeParams)
if err != nil {
return fmt.Errorf("failed to load Store: %v", err)
}
newStores[key] = store
}
// Success.
rs.lastCommitID = cInfo.CommitID()
rs.stores = newStores
return nil
}
// SetTracer sets the tracer for the MultiStore that the underlying
// stores will utilize to trace operations. A MultiStore is returned.
func (rs *Store) SetTracer(w io.Writer) types.MultiStore {
rs.traceWriter = w
return rs
}
// SetTracingContext updates the tracing context for the MultiStore by merging
// the given context with the existing context by key. Any existing keys will
// be overwritten. It is implied that the caller should update the context when
// necessary between tracing operations. It returns a modified MultiStore.
func (rs *Store) SetTracingContext(tc types.TraceContext) types.MultiStore {
if rs.traceContext != nil {
for k, v := range tc {
rs.traceContext[k] = v
}
} else {
rs.traceContext = tc
}
return rs
}
// TracingEnabled returns if tracing is enabled for the MultiStore.
func (rs *Store) TracingEnabled() bool {
return rs.traceWriter != nil
}
//----------------------------------------
// +CommitStore
// Implements Committer/CommitStore.
func (rs *Store) LastCommitID() types.CommitID {
return rs.lastCommitID
}
// Implements Committer/CommitStore.
func (rs *Store) Commit() types.CommitID {
// Commit stores.
version := rs.lastCommitID.Version + 1
commitInfo := commitStores(version, rs.stores)
// Need to update atomically.
batch := rs.db.NewBatch()
setCommitInfo(batch, version, commitInfo)
setLatestVersion(batch, version)
batch.Write()
// Prepare for next version.
commitID := types.CommitID{
Version: version,
Hash: commitInfo.Hash(),
}
rs.lastCommitID = commitID
return commitID
}
// Implements CacheWrapper/Store/CommitStore.
func (rs *Store) CacheWrap() types.CacheWrap {
return rs.CacheMultiStore().(types.CacheWrap)
}
// CacheWrapWithTrace implements the CacheWrapper interface.
func (rs *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap {
return rs.CacheWrap()
}
//----------------------------------------
// +MultiStore
// Implements MultiStore.
func (rs *Store) CacheMultiStore() types.CacheMultiStore {
stores := make(map[types.StoreKey]types.CacheWrapper)
for k, v := range rs.stores {
stores[k] = v
}
return cachemulti.NewStore(rs.db, stores, rs.keysByName, rs.traceWriter, rs.traceContext)
}
// Implements MultiStore.
// If the store does not exist, panics.
func (rs *Store) GetStore(key types.StoreKey) types.Store {
store := rs.stores[key]
if store == nil {
panic("Could not load store " + key.String())
}
return store
}
// GetKVStore implements the MultiStore interface. If tracing is enabled on the
// Store, a wrapped TraceKVStore will be returned with the given
// tracer, otherwise, the original KVStore will be returned.
// If the store does not exist, panics.
func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore {
store := rs.stores[key].(types.KVStore)
if rs.TracingEnabled() {
store = tracekv.NewStore(store, rs.traceWriter, rs.traceContext)
}
return store
}
// Implements MultiStore
// 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 *Store) getStoreByName(name string) types.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 *Store) 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.QueryResult()
}
store := rs.getStoreByName(storeName)
if store == nil {
msg := fmt.Sprintf("no such store: %s", storeName)
return types.ErrUnknownRequest(msg).QueryResult()
}
queryable, ok := store.(types.Queryable)
if !ok {
msg := fmt.Sprintf("store %s doesn't support queries", storeName)
return types.ErrUnknownRequest(msg).QueryResult()
}
// trim the path and make the query
req.Path = subpath
res := queryable.Query(req)
if !req.Prove || !RequireProof(subpath) {
return res
}
if res.Proof == nil || len(res.Proof.Ops) == 0 {
return types.ErrInternal("substore proof was nil/empty when it should never be").QueryResult()
}
commitInfo, errMsg := getCommitInfo(rs.db, res.Height)
if errMsg != nil {
return types.ErrInternal(errMsg.Error()).QueryResult()
}
// Restore origin path and append proof op.
res.Proof.Ops = append(res.Proof.Ops, NewMultiStoreProofOp(
[]byte(storeName),
NewMultiStoreProof(commitInfo.StoreInfos),
).ProofOp())
// TODO: handle in another TM v0.26 update PR
// res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos)
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 types.Error) {
if !strings.HasPrefix(path, "/") {
err = types.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 *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (store types.CommitStore, err error) {
var db dbm.DB
if params.db != nil {
db = dbm.NewPrefixDB(params.db, []byte("s/_/"))
} else {
db = dbm.NewPrefixDB(rs.db, []byte("s/k:"+params.key.Name()+"/"))
}
switch params.typ {
case types.StoreTypeMulti:
panic("recursive MultiStores not yet supported")
// TODO: id?
// return NewCommitMultiStore(db, id)
case types.StoreTypeIAVL:
store, err = iavl.LoadStore(db, id, rs.pruningOpts)
return
case types.StoreTypeDB:
store = commitDBStoreAdapter{dbadapter.Store{db}}
return
case types.StoreTypeTransient:
_, ok := key.(*types.TransientStoreKey)
if !ok {
err = fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String())
return
}
store = transient.NewStore()
return
default:
panic(fmt.Sprintf("unrecognized store type %v", params.typ))
}
}
func (rs *Store) nameToKey(name string) types.StoreKey {
for key := range rs.storesParams {
if key.Name() == name {
return key
}
}
panic("Unknown name " + name)
}
//----------------------------------------
// storeParams
type storeParams struct {
key types.StoreKey
db dbm.DB
typ types.StoreType
}
//----------------------------------------
// commitInfo
// NOTE: Keep commitInfo a simple immutable struct.
type commitInfo struct {
// Version
Version int64
// Store info for
StoreInfos []storeInfo
}
// Hash returns the simple merkle root hash of the stores sorted by name.
func (ci commitInfo) Hash() []byte {
// TODO: cache to ci.hash []byte
m := make(map[string][]byte, len(ci.StoreInfos))
for _, storeInfo := range ci.StoreInfos {
m[storeInfo.Name] = storeInfo.Hash()
}
return merkle.SimpleHashFromMap(m)
}
func (ci commitInfo) CommitID() types.CommitID {
return types.CommitID{
Version: ci.Version,
Hash: ci.Hash(),
}
}
//----------------------------------------
// storeInfo
// storeInfo contains the name and core reference for an
// underlying store. It is the leaf of the Stores top
// level simple merkle tree.
type storeInfo struct {
Name string
Core storeCore
}
type storeCore struct {
// StoreType StoreType
CommitID types.CommitID
// ... maybe add more state
}
// Implements merkle.Hasher.
func (si storeInfo) Hash() []byte {
// Doesn't write Name, since merkle.SimpleHashFromMap() will
// include them via the keys.
bz, _ := cdc.MarshalBinaryLengthPrefixed(si.Core)
hasher := tmhash.New()
_, err := hasher.Write(bz)
if err != nil {
// TODO: Handle with #870
panic(err)
}
return hasher.Sum(nil)
}
//----------------------------------------
// Misc.
func getLatestVersion(db dbm.DB) int64 {
var latest int64
latestBytes := db.Get([]byte(latestVersionKey))
if latestBytes == nil {
return 0
}
err := cdc.UnmarshalBinaryLengthPrefixed(latestBytes, &latest)
if err != nil {
panic(err)
}
return latest
}
// Set the latest version.
func setLatestVersion(batch dbm.Batch, version int64) {
latestBytes, _ := cdc.MarshalBinaryLengthPrefixed(version)
batch.Set([]byte(latestVersionKey), latestBytes)
}
// Commits each store and returns a new commitInfo.
func commitStores(version int64, storeMap map[types.StoreKey]types.CommitStore) commitInfo {
storeInfos := make([]storeInfo, 0, len(storeMap))
for key, store := range storeMap {
// Commit
commitID := store.Commit()
if store.GetStoreType() == types.StoreTypeTransient {
continue
}
// Record CommitID
si := storeInfo{}
si.Name = key.Name()
si.Core.CommitID = commitID
// si.Core.StoreType = store.GetStoreType()
storeInfos = append(storeInfos, si)
}
ci := commitInfo{
Version: version,
StoreInfos: storeInfos,
}
return ci
}
// Gets commitInfo from disk.
func getCommitInfo(db dbm.DB, ver int64) (commitInfo, error) {
// Get from DB.
cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver)
cInfoBytes := db.Get([]byte(cInfoKey))
if cInfoBytes == nil {
return commitInfo{}, fmt.Errorf("failed to get Store: no data")
}
var cInfo commitInfo
err := cdc.UnmarshalBinaryLengthPrefixed(cInfoBytes, &cInfo)
if err != nil {
return commitInfo{}, fmt.Errorf("failed to get Store: %v", err)
}
return cInfo, nil
}
// Set a commitInfo for given version.
func setCommitInfo(batch dbm.Batch, version int64, cInfo commitInfo) {
cInfoBytes := cdc.MustMarshalBinaryLengthPrefixed(cInfo)
cInfoKey := fmt.Sprintf(commitInfoKeyFmt, version)
batch.Set([]byte(cInfoKey), cInfoBytes)
}
+235
View File
@@ -0,0 +1,235 @@
package rootmulti
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/merkle"
dbm "github.com/tendermint/tendermint/libs/db"
stypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/types"
)
const useDebugDB = false
func TestStoreType(t *testing.T) {
db := dbm.NewMemDB()
store := NewStore(db)
store.MountStoreWithDB(
types.NewKVStoreKey("store1"), types.StoreTypeIAVL, db)
}
func TestStoreMount(t *testing.T) {
db := dbm.NewMemDB()
store := NewStore(db)
key1 := types.NewKVStoreKey("store1")
key2 := types.NewKVStoreKey("store2")
dup1 := types.NewKVStoreKey("store1")
require.NotPanics(t, func() { store.MountStoreWithDB(key1, types.StoreTypeIAVL, db) })
require.NotPanics(t, func() { store.MountStoreWithDB(key2, types.StoreTypeIAVL, db) })
require.Panics(t, func() { store.MountStoreWithDB(key1, types.StoreTypeIAVL, db) })
require.Panics(t, func() { store.MountStoreWithDB(dup1, types.StoreTypeIAVL, db) })
}
func TestMultistoreCommitLoad(t *testing.T) {
var db dbm.DB = dbm.NewMemDB()
if useDebugDB {
db = dbm.NewDebugDB("CMS", db)
}
store := newMultiStoreWithMounts(db)
err := store.LoadLatestVersion()
require.Nil(t, err)
// New store has empty last commit.
commitID := types.CommitID{}
checkStore(t, store, commitID, commitID)
// Make sure we can get stores by name.
s1 := store.getStoreByName("store1")
require.NotNil(t, s1)
s3 := store.getStoreByName("store3")
require.NotNil(t, s3)
s77 := store.getStoreByName("store77")
require.Nil(t, s77)
// Make a few commits and check them.
nCommits := int64(3)
for i := int64(0); i < nCommits; i++ {
commitID = store.Commit()
expectedCommitID := getExpectedCommitID(store, i+1)
checkStore(t, store, expectedCommitID, commitID)
}
// Load the latest multistore again and check version.
store = newMultiStoreWithMounts(db)
err = store.LoadLatestVersion()
require.Nil(t, err)
commitID = getExpectedCommitID(store, nCommits)
checkStore(t, store, commitID, commitID)
// Commit and check version.
commitID = store.Commit()
expectedCommitID := getExpectedCommitID(store, nCommits+1)
checkStore(t, store, expectedCommitID, commitID)
// Load an older multistore and check version.
ver := nCommits - 1
store = newMultiStoreWithMounts(db)
err = store.LoadVersion(ver)
require.Nil(t, err)
commitID = getExpectedCommitID(store, ver)
checkStore(t, store, commitID, commitID)
// XXX: commit this older version
commitID = store.Commit()
expectedCommitID = getExpectedCommitID(store, ver+1)
checkStore(t, store, expectedCommitID, commitID)
// XXX: confirm old commit is overwritten and we have rolled back
// LatestVersion
store = newMultiStoreWithMounts(db)
err = store.LoadLatestVersion()
require.Nil(t, err)
commitID = getExpectedCommitID(store, ver+1)
checkStore(t, store, commitID, commitID)
}
func TestParsePath(t *testing.T) {
_, _, err := parsePath("foo")
require.Error(t, err)
store, subpath, err := parsePath("/foo")
require.NoError(t, err)
require.Equal(t, store, "foo")
require.Equal(t, subpath, "")
store, subpath, err = parsePath("/fizz/bang/baz")
require.NoError(t, err)
require.Equal(t, store, "fizz")
require.Equal(t, subpath, "/bang/baz")
substore, subsubpath, err := parsePath(subpath)
require.NoError(t, err)
require.Equal(t, substore, "bang")
require.Equal(t, subsubpath, "/baz")
}
func TestMultiStoreQuery(t *testing.T) {
db := dbm.NewMemDB()
multi := newMultiStoreWithMounts(db)
err := multi.LoadLatestVersion()
require.Nil(t, err)
k, v := []byte("wind"), []byte("blows")
k2, v2 := []byte("water"), []byte("flows")
// v3 := []byte("is cold")
cid := multi.Commit()
// Make sure we can get by name.
garbage := multi.getStoreByName("bad-name")
require.Nil(t, garbage)
// Set and commit data in one store.
store1 := multi.getStoreByName("store1").(types.KVStore)
store1.Set(k, v)
// ... and another.
store2 := multi.getStoreByName("store2").(types.KVStore)
store2.Set(k2, v2)
// Commit the multistore.
cid = multi.Commit()
ver := cid.Version
// Reload multistore from database
multi = newMultiStoreWithMounts(db)
err = multi.LoadLatestVersion()
require.Nil(t, err)
// Test bad path.
query := abci.RequestQuery{Path: "/key", Data: k, Height: ver}
qres := multi.Query(query)
require.EqualValues(t, types.CodeUnknownRequest, qres.Code)
require.EqualValues(t, types.CodespaceRoot, qres.Codespace)
query.Path = "h897fy32890rf63296r92"
qres = multi.Query(query)
require.EqualValues(t, types.CodeUnknownRequest, qres.Code)
require.EqualValues(t, types.CodespaceRoot, qres.Codespace)
// Test invalid store name.
query.Path = "/garbage/key"
qres = multi.Query(query)
require.EqualValues(t, types.CodeUnknownRequest, qres.Code)
require.EqualValues(t, types.CodespaceRoot, qres.Codespace)
// Test valid query with data.
query.Path = "/store1/key"
qres = multi.Query(query)
require.EqualValues(t, types.CodeOK, qres.Code)
require.Equal(t, v, qres.Value)
// Test valid but empty query.
query.Path = "/store2/key"
query.Prove = true
qres = multi.Query(query)
require.EqualValues(t, types.CodeOK, qres.Code)
require.Nil(t, qres.Value)
// Test store2 data.
query.Data = k2
qres = multi.Query(query)
require.EqualValues(t, types.CodeOK, qres.Code)
require.Equal(t, v2, qres.Value)
}
//-----------------------------------------------------------------------
// utils
func newMultiStoreWithMounts(db dbm.DB) *Store {
store := NewStore(db)
store.pruningOpts = stypes.PruneSyncable
store.MountStoreWithDB(
types.NewKVStoreKey("store1"), types.StoreTypeIAVL, nil)
store.MountStoreWithDB(
types.NewKVStoreKey("store2"), types.StoreTypeIAVL, nil)
store.MountStoreWithDB(
types.NewKVStoreKey("store3"), types.StoreTypeIAVL, nil)
return store
}
func checkStore(t *testing.T, store *Store, expect, got types.CommitID) {
require.Equal(t, expect, got)
require.Equal(t, expect, store.LastCommitID())
}
func getExpectedCommitID(store *Store, ver int64) types.CommitID {
return types.CommitID{
Version: ver,
Hash: hashStores(store.stores),
}
}
func hashStores(stores map[types.StoreKey]types.CommitStore) []byte {
m := make(map[string][]byte, len(stores))
for key, store := range stores {
name := key.Name()
m[name] = storeInfo{
Name: name,
Core: storeCore{
CommitID: store.LastCommitID(),
// StoreType: store.GetStoreType(),
},
}.Hash()
}
return merkle.SimpleHashFromMap(m)
}
+7
View File
@@ -0,0 +1,7 @@
package rootmulti
import (
"github.com/cosmos/cosmos-sdk/codec"
)
var cdc = codec.New()