Skip to content

Commit

Permalink
chore: cleanup store/v2 interface (#20280)
Browse files Browse the repository at this point in the history
  • Loading branch information
cool-develope committed May 13, 2024
1 parent 11de280 commit 8d1946c
Show file tree
Hide file tree
Showing 19 changed files with 244 additions and 215 deletions.
50 changes: 50 additions & 0 deletions core/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,56 @@ type KVStore interface {
ReverseIterator(start, end []byte) (Iterator, error)
}

// Batch represents a group of writes. They may or may not be written atomically depending on the
// backend. Callers must call Close on the batch when done.
//
// As with KVStore, given keys and values should be considered read-only, and must not be modified after
// passing them to the batch.
type Batch interface {
// Set sets a key/value pair.
// CONTRACT: key, value readonly []byte
Set(key, value []byte) error

// Delete deletes a key/value pair.
// CONTRACT: key readonly []byte
Delete(key []byte) error

// Write writes the batch, possibly without flushing to disk. Only Close() can be called after,
// other methods will error.
Write() error

// WriteSync writes the batch and flushes it to disk. Only Close() can be called after, other
// methods will error.
WriteSync() error

// Close closes the batch. It is idempotent, but calls to other methods afterwards will error.
Close() error

// GetByteSize that returns the current size of the batch in bytes. Depending on the implementation,
// this may return the size of the underlying LSM batch, including the size of additional metadata
// on top of the expected key and value total byte count.
GetByteSize() (int, error)
}

// BatchCreator defines an interface for creating a new batch.
type BatchCreator interface {
// NewBatch creates a new batch for atomic updates. The caller must call Batch.Close.
NewBatch() Batch

// NewBatchWithSize create a new batch for atomic updates, but with pre-allocated size.
// This will does the same thing as NewBatch if the batch implementation doesn't support pre-allocation.
NewBatchWithSize(int) Batch
}

// KVStoreWithBatch is an extension of KVStore that allows for batch writes.
type KVStoreWithBatch interface {
KVStore
BatchCreator

// Close closes the KVStoreWithBatch, releasing any resources held.
Close() error
}

// Iterator represents an iterator over a domain of keys. Callers must call
// Close when done. No writes can happen to a domain while there exists an
// iterator over it. Some backends may take out database locks to ensure this
Expand Down
41 changes: 9 additions & 32 deletions store/v2/batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ package store
// Batch is a write-only database that commits changes to the underlying database
// when Write is called. A batch cannot be used concurrently.
type Batch interface {
Writer
// Set inserts the given value into the key-value data store.
//
// Note: <key, value> are safe to modify and read after calling Set.
Set(storeKey, key, value []byte) error

// Delete removes the key from the backing key-value data store.
//
// Note: <key> is safe to modify and read after calling Delete.
Delete(storeKey, key []byte) error

// Size retrieves the amount of data queued up for writing, this includes
// the keys, values, and deleted keys.
Expand All @@ -15,34 +23,3 @@ type Batch interface {
// Reset resets the batch.
Reset() error
}

// RawBatch represents a group of writes. They may or may not be written atomically depending on the
// backend. Callers must call Close on the batch when done.
//
// As with RawDB, given keys and values should be considered read-only, and must not be modified after
// passing them to the batch.
type RawBatch interface {
// Set sets a key/value pair.
// CONTRACT: key, value readonly []byte
Set(key, value []byte) error

// Delete deletes a key/value pair.
// CONTRACT: key readonly []byte
Delete(key []byte) error

// Write writes the batch, possibly without flushing to disk. Only Close() can be called after,
// other methods will error.
Write() error

// WriteSync writes the batch and flushes it to disk. Only Close() can be called after, other
// methods will error.
WriteSync() error

// Close closes the batch. It is idempotent, but calls to other methods afterwards will error.
Close() error

// GetByteSize that returns the current size of the batch in bytes. Depending on the implementation,
// this may return the size of the underlying LSM batch, including the size of additional metadata
// on top of the expected key and value total byte count.
GetByteSize() (int, error)
}
12 changes: 6 additions & 6 deletions store/v2/commitment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ into store/v2, specifically the `RootStore` type.
A foremost design goal is that SC backends should be easily swappable, i.e. not
necessarily IAVL. To this end, the scope of SC has been reduced, it must only:

* Provide a stateful root app hash for height h resulting from applying a batch
- Provide a stateful root app hash for height h resulting from applying a batch
of key-value set/deletes to height h-1.
* Fulfill (though not necessarily provide) historical proofs for all heights < `h`.
* Provide an API for snapshot create/restore to fulfill state sync requests.
- Fulfill (though not necessarily provide) historical proofs for all heights < `h`.
- Provide an API for snapshot create/restore to fulfill state sync requests.

Notably, SC is not required to provide key iteration or value retrieval for either
queries or state machine execution, this now being the responsibility of state
Expand Down Expand Up @@ -42,6 +42,6 @@ and `Restore` methods.

Similar to the `storage` package, the `commitment` package is designed to be used
in a broader store implementation, i.e. it fulfills the role of the SC backend.
Specifically, it provides a `CommitStore` type which accepts a `store.RawDB` and
a mapping from store key, a string meant to represent a single module, to a `Tree`,
which reflects the commitment structure.
Specifically, it provides a `CommitStore` type which accepts a `corestore.KVStore`
and a mapping from store key, a string meant to represent a single module, to a
`Tree`, which reflects the commitment structure.
4 changes: 2 additions & 2 deletions store/v2/commitment/iavl/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"github.com/cosmos/iavl"
ics23 "github.com/cosmos/ics23/go"

corestore "cosmossdk.io/core/store"
"cosmossdk.io/log"
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/commitment"
dbm "cosmossdk.io/store/v2/db"
)
Expand All @@ -20,7 +20,7 @@ type IavlTree struct {
}

// NewIavlTree creates a new IavlTree instance.
func NewIavlTree(db store.RawDB, logger log.Logger, cfg *Config) *IavlTree {
func NewIavlTree(db corestore.KVStoreWithBatch, logger log.Logger, cfg *Config) *IavlTree {
tree := iavl.NewMutableTree(dbm.NewWrapper(db), cfg.CacheSize, cfg.SkipFastStorageUpgrade, logger)
return &IavlTree{
tree: tree,
Expand Down
3 changes: 2 additions & 1 deletion store/v2/commitment/iavl/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

corestore "cosmossdk.io/core/store"
"cosmossdk.io/log"
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/commitment"
Expand All @@ -14,7 +15,7 @@ import (

func TestCommitterSuite(t *testing.T) {
s := &commitment.CommitStoreTestSuite{
NewStore: func(db store.RawDB, storeKeys []string, pruneOpts *store.PruneOptions, logger log.Logger) (*commitment.CommitStore, error) {
NewStore: func(db corestore.KVStoreWithBatch, storeKeys []string, pruneOpts *store.PruneOptions, logger log.Logger) (*commitment.CommitStore, error) {
multiTrees := make(map[string]commitment.Tree)
cfg := DefaultConfig()
for _, storeKey := range storeKeys {
Expand Down
4 changes: 2 additions & 2 deletions store/v2/commitment/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ var (
// and trees.
type CommitStore struct {
logger log.Logger
db store.RawDB
db corestore.KVStoreWithBatch
multiTrees map[string]Tree

// pruneOptions is the pruning configuration.
pruneOptions *store.PruneOptions
}

// NewCommitStore creates a new CommitStore instance.
func NewCommitStore(trees map[string]Tree, db store.RawDB, pruneOpts *store.PruneOptions, logger log.Logger) (*CommitStore, error) {
func NewCommitStore(trees map[string]Tree, db corestore.KVStoreWithBatch, pruneOpts *store.PruneOptions, logger log.Logger) (*CommitStore, error) {
if pruneOpts == nil {
pruneOpts = store.DefaultPruneOptions()
}
Expand Down
2 changes: 1 addition & 1 deletion store/v2/commitment/store_test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const (
type CommitStoreTestSuite struct {
suite.Suite

NewStore func(db store.RawDB, storeKeys []string, pruneOpts *store.PruneOptions, logger log.Logger) (*CommitStore, error)
NewStore func(db corestore.KVStoreWithBatch, storeKeys []string, pruneOpts *store.PruneOptions, logger log.Logger) (*CommitStore, error)
}

func (s *CommitStoreTestSuite) TestStore_Snapshotter() {
Expand Down
77 changes: 0 additions & 77 deletions store/v2/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,6 @@ import (
"cosmossdk.io/store/v2/proof"
)

// Reader wraps the Has and Get method of a backing data store.
type Reader interface {
// Has retrieves if a key is present in the key-value data store.
//
// Note: <key> is safe to modify and read after calling Has.
Has(storeKey, key []byte) (bool, error)

// Get retrieves the given key if it's present in the key-value data store.
//
// Note: <key> is safe to modify and read after calling Get.
// The returned byte slice is safe to read, but cannot be modified.
Get(storeKey, key []byte) ([]byte, error)
}

// Writer wraps the Set method of a backing data store.
type Writer interface {
// Set inserts the given value into the key-value data store.
//
// Note: <key, value> are safe to modify and read after calling Set.
Set(storeKey, key, value []byte) error

// Delete removes the key from the backing key-value data store.
//
// Note: <key> is safe to modify and read after calling Delete.
Delete(storeKey, key []byte) error
}

// Database contains all the methods required to allow handling different
// key-value data stores backing the database.
type Database interface {
Reader
Writer
corestore.IteratorCreator
io.Closer
}

// VersionedDatabase defines an API for a versioned database that allows reads,
// writes, iteration and commitment over a series of versions.
type VersionedDatabase interface {
Expand Down Expand Up @@ -107,44 +71,3 @@ type Committer interface {
// only be called once and any call after may panic.
io.Closer
}

// RawDB is the main interface for all key-value database backends. DBs are concurrency-safe.
// Callers must call Close on the database when done.
//
// Keys cannot be nil or empty, while values cannot be nil. Keys and values should be considered
// read-only, both when returned and when given, and must be copied before they are modified.
type RawDB interface {
// Get fetches the value of the given key, or nil if it does not exist.
// CONTRACT: key, value readonly []byte
Get([]byte) ([]byte, error)

// Has checks if a key exists.
// CONTRACT: key, value readonly []byte
Has(key []byte) (bool, error)

// Iterator returns an iterator over a domain of keys, in ascending order. The caller must call
// Close when done. End is exclusive, and start must be less than end. A nil start iterates
// from the first key, and a nil end iterates to the last key (inclusive). Empty keys are not
// valid.
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
// CONTRACT: start, end readonly []byte
Iterator(start, end []byte) (corestore.Iterator, error)

// ReverseIterator returns an iterator over a domain of keys, in descending order. The caller
// must call Close when done. End is exclusive, and start must be less than end. A nil end
// iterates from the last key (inclusive), and a nil start iterates to the first key (inclusive).
// Empty keys are not valid.
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
// CONTRACT: start, end readonly []byte
ReverseIterator(start, end []byte) (corestore.Iterator, error)

// Close closes the database connection.
Close() error

// NewBatch creates a batch for atomic updates. The caller must call Batch.Close.
NewBatch() RawBatch

// NewBatchWithSize create a new batch for atomic updates, but with pre-allocated size.
// This will does the same thing as NewBatch if the batch implementation doesn't support pre-allocation.
NewBatchWithSize(int) RawBatch
}
13 changes: 7 additions & 6 deletions store/v2/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ package db
import (
"fmt"

corestore "cosmossdk.io/core/store"
"cosmossdk.io/store/v2"
)

type RawDBType string
type DBType string

const (
DBTypeGoLevelDB RawDBType = "goleveldb"
DBTypeRocksDB RawDBType = "rocksdb"
DBTypePebbleDB RawDBType = "pebbledb"
DBTypePrefixDB RawDBType = "prefixdb"
DBTypeGoLevelDB DBType = "goleveldb"
DBTypeRocksDB DBType = "rocksdb"
DBTypePebbleDB DBType = "pebbledb"
DBTypePrefixDB DBType = "prefixdb"

DBFileSuffix string = ".db"
)

func NewRawDB(dbType RawDBType, name, dataDir string, opts store.DBOptions) (store.RawDB, error) {
func NewDB(dbType DBType, name, dataDir string, opts store.DBOptions) (corestore.KVStoreWithBatch, error) {
switch dbType {
case DBTypeGoLevelDB:
return NewGoLevelDB(name, dataDir, opts)
Expand Down
24 changes: 20 additions & 4 deletions store/v2/db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"cosmossdk.io/store/v2"
corestore "cosmossdk.io/core/store"
)

type DBTestSuite struct {
suite.Suite

db store.RawDB
db corestore.KVStoreWithBatch
}

func (s *DBTestSuite) TearDownSuite() {
s.Require().NoError(s.db.Close())
}

func (s *DBTestSuite) TestDBOperations() {
// Set
// Batch Set
b := s.db.NewBatch()
s.Require().NoError(b.Set([]byte("key"), []byte("value")))
s.Require().NoError(b.Set([]byte("key1"), []byte("value1")))
Expand All @@ -41,7 +41,7 @@ func (s *DBTestSuite) TestDBOperations() {
s.Require().NoError(err)
s.Require().False(has)

// Delete
// Batch Delete
b = s.db.NewBatch()
s.Require().NoError(b.Delete([]byte("key1")))
s.Require().NoError(b.Write())
Expand All @@ -50,6 +50,22 @@ func (s *DBTestSuite) TestDBOperations() {
has, err = s.db.Has([]byte("key1"))
s.Require().NoError(err)
s.Require().False(has)

// Set & Delete
s.Require().NoError(s.db.Set([]byte("key3"), []byte("value3")))
has, err = s.db.Has([]byte("key3"))
s.Require().NoError(err)
s.Require().True(has)
value, err = s.db.Get([]byte("key3"))
s.Require().NoError(err)
s.Require().Equal([]byte("value3"), value)
s.Require().NoError(s.db.Delete([]byte("key3")))
has, err = s.db.Has([]byte("key3"))
s.Require().NoError(err)
s.Require().False(has)
value, err = s.db.Get([]byte("key3"))
s.Require().NoError(err)
s.Require().Nil(value)
}

func (s *DBTestSuite) TestIterator() {
Expand Down

0 comments on commit 8d1946c

Please sign in to comment.