Store Refactor 1 (#2985)
This commit is contained in:
@@ -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) {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package rootmulti
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
var cdc = codec.New()
|
||||
Reference in New Issue
Block a user