Skip to content

Commit

Permalink
feat: RootStore PoC (#17577)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored Sep 28, 2023
1 parent 5431a69 commit 81cd74d
Show file tree
Hide file tree
Showing 18 changed files with 814 additions and 310 deletions.
104 changes: 104 additions & 0 deletions store/branch/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package branch

import (
"io"
"slices"

"golang.org/x/exp/maps"

"cosmossdk.io/store/v2"
)

var (
_ store.KVStore = (*Store)(nil)
_ store.BranchedKVStore = (*Store)(nil)
)

// Store implements both a KVStore and BranchedKVStore interfaces. It is used to
// accumulate writes that can be later committed to backing SS and SC engines or
// discarded altogether. If a read is not found through an uncommitted write, it
// will be delegated to the SS backend.
type Store struct {
// storage reflects backing storage (SS) for reads that are not found in uncommitted volatile state
storage store.VersionedDatabase

// storeKey reflects the store key used for the store
storeKey string

// parent reflects a parent store if branched (it may be nil)
// parent store.KVStore

// changeSet reflects the uncommitted writes to the store
//
// Note, this field might be removed depending on how the branching fields
// below are defined and used.
changeSet map[string]store.KVPair

// TODO: Fields for branching functionality. These fields should most likely
// reflect what currently exists in cachekv.Store.
}

func New(storeKey string, ss store.VersionedDatabase) store.KVStore {
return &Store{
storage: ss,
storeKey: storeKey,
changeSet: make(map[string]store.KVPair),
}
}

func (s *Store) GetStoreType() store.StoreType {
return store.StoreTypeBranch
}

// GetChangeSet returns the uncommitted writes to the store, ordered by key.
func (s *Store) GetChangeSet() *store.ChangeSet {
keys := maps.Keys(s.changeSet)
slices.Sort(keys)

pairs := make([]store.KVPair, len(keys))
for i, key := range keys {
pairs[i] = s.changeSet[key]
}

return store.NewChangeSet(pairs...)
}

func (s *Store) Reset() {
clear(s.changeSet)
}

func (s *Store) Branch() store.BranchedKVStore {
panic("not implemented!")
}

func (s *Store) BranchWithTrace(w io.Writer, tc store.TraceContext) store.BranchedKVStore {
panic("not implemented!")
}

func (s *Store) Iterator(start, end []byte) store.Iterator {
panic("not implemented!")
}

func (s *Store) ReverseIterator(start, end []byte) store.Iterator {
panic("not implemented!")
}

func (s *Store) Get(key []byte) []byte {
panic("not implemented!")
}

func (s *Store) Has(key []byte) bool {
panic("not implemented!")
}

func (s *Store) Set(key, value []byte) {
panic("not implemented!")
}

func (s *Store) Delete(key []byte) {
panic("not implemented!")
}

func (s *Store) Write() {
panic("not implemented!")
}
39 changes: 39 additions & 0 deletions store/change_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package store

// KVPair defines a key-value pair with additional metadata that is used to
// track writes. Deletion can be denoted by a nil value or explicitly by the
// Delete field.
type KVPair struct {
Key []byte
Value []byte
StoreKey string // optional
}

// ChangeSet defines a set of KVPair entries.
type ChangeSet struct {
Pairs []KVPair
}

func NewChangeSet(pairs ...KVPair) *ChangeSet {
return &ChangeSet{
Pairs: pairs,
}
}

// Size returns the number of key-value pairs in the batch.
func (cs *ChangeSet) Size() int {
return len(cs.Pairs)
}

// Add adds a key-value pair to the ChangeSet.
func (cs *ChangeSet) Add(key, value []byte) {
cs.Pairs = append(cs.Pairs, KVPair{
Key: key,
Value: value,
})
}

// AddKVPair adds a KVPair to the ChangeSet.
func (cs *ChangeSet) AddKVPair(pair KVPair) {
cs.Pairs = append(cs.Pairs, pair)
}
82 changes: 82 additions & 0 deletions store/commit_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package store

import (
"fmt"
"time"

"cosmossdk.io/store/v2/internal/maps"
)

type (
// CommitHeader defines the interface for a block header that can be provided
// to a MultiStore upon Commit. This should be optional and used to facilitate
// time-based queries only.
CommitHeader interface {
GetTime() time.Time
GetHeight() uint64
}

// CommitInfo defines commit information used by the multi-store when committing
// a version/height.
CommitInfo struct {
Version uint64
StoreInfos []StoreInfo
Timestamp time.Time
}

// StoreInfo defines store-specific commit information. It contains a reference
// between a store name/key and the commit ID.
StoreInfo struct {
Name string
CommitID CommitID
}

// CommitID defines the commitment information when a specific store is
// committed.
CommitID struct {
Version uint64
Hash []byte
}
)

func (si StoreInfo) GetHash() []byte {
return si.CommitID.Hash
}

// Hash returns the root hash of all committed stores represented by CommitInfo,
// sorted by store name/key.
func (ci CommitInfo) Hash() []byte {
if len(ci.StoreInfos) == 0 {
return nil
}

rootHash, _, _ := maps.ProofsFromMap(ci.toMap())
return rootHash
}

func (ci CommitInfo) toMap() map[string][]byte {
m := make(map[string][]byte, len(ci.StoreInfos))
for _, storeInfo := range ci.StoreInfos {
m[storeInfo.Name] = storeInfo.GetHash()
}

return m
}

func (ci CommitInfo) CommitID() CommitID {
return CommitID{
Version: ci.Version,
Hash: ci.Hash(),
}
}

func (m *CommitInfo) GetVersion() uint64 {
if m != nil {
return m.Version
}
return 0
}

func (cid CommitID) String() string {
return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version)
}
3 changes: 3 additions & 0 deletions store/commitment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# State Commitment (SC)

TODO
10 changes: 5 additions & 5 deletions store/commitment/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,30 @@ import (

ics23 "github.com/cosmos/ics23/go"

"cosmossdk.io/store/v2/commitment/types"
"cosmossdk.io/store/v2"
)

// Database represents a state commitment store. It is designed to securely store
// and manage the most recent state information, crucial for achieving consensus.
// Each module creates its own instance of Database for managing its specific state.
type Database struct {
mu sync.Mutex
tree types.Tree
tree store.Tree
}

// NewDatabase creates a new Database instance.
func NewDatabase(tree types.Tree) *Database {
func NewDatabase(tree store.Tree) *Database {
return &Database{
tree: tree,
}
}

// WriteBatch writes a batch of key-value pairs to the database.
func (db *Database) WriteBatch(batch *types.Batch) error {
func (db *Database) WriteBatch(cs *store.ChangeSet) error {
db.mu.Lock()
defer db.mu.Unlock()

return db.tree.WriteBatch(batch)
return db.tree.WriteBatch(cs)
}

// WorkingHash returns the working hash of the database.
Expand Down
30 changes: 15 additions & 15 deletions store/commitment/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"github.com/stretchr/testify/require"

"cosmossdk.io/log"
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/commitment/iavl"
"cosmossdk.io/store/v2/commitment/types"
)

func generateTree(treeType string) types.Tree {
func generateTree(treeType string) store.Tree {
if treeType == "iavl" {
cfg := iavl.DefaultConfig()
db := dbm.NewMemDB()
Expand All @@ -28,16 +28,16 @@ func TestIavlTree(t *testing.T) {
tree := generateTree("iavl")
require.NotNil(t, tree)

intialVersion := tree.GetLatestVersion()
require.Equal(t, uint64(0), intialVersion)
initVersion := tree.GetLatestVersion()
require.Equal(t, uint64(0), initVersion)

// write a batch of version 1
batch1 := types.NewBatch()
batch1.Add([]byte("key1"), []byte("value1"))
batch1.Add([]byte("key2"), []byte("value2"))
batch1.Add([]byte("key3"), []byte("value3"))
cs1 := store.NewChangeSet()
cs1.Add([]byte("key1"), []byte("value1"))
cs1.Add([]byte("key2"), []byte("value2"))
cs1.Add([]byte("key3"), []byte("value3"))

err := tree.WriteBatch(batch1)
err := tree.WriteBatch(cs1)
require.NoError(t, err)

workingHash := tree.WorkingHash()
Expand All @@ -52,12 +52,12 @@ func TestIavlTree(t *testing.T) {
version1Hash := tree.WorkingHash()

// write a batch of version 2
batch2 := types.NewBatch()
batch2.Add([]byte("key4"), []byte("value4"))
batch2.Add([]byte("key5"), []byte("value5"))
batch2.Add([]byte("key6"), []byte("value6"))
batch2.Add([]byte("key1"), nil) // delete key1
err = tree.WriteBatch(batch2)
cs2 := store.NewChangeSet()
cs2.Add([]byte("key4"), []byte("value4"))
cs2.Add([]byte("key5"), []byte("value5"))
cs2.Add([]byte("key6"), []byte("value6"))
cs2.Add([]byte("key1"), nil) // delete key1
err = tree.WriteBatch(cs2)
require.NoError(t, err)
workingHash = tree.WorkingHash()
require.NotNil(t, workingHash)
Expand Down
8 changes: 4 additions & 4 deletions store/commitment/iavl/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
ics23 "github.com/cosmos/ics23/go"

log "cosmossdk.io/log"
commitmenttypes "cosmossdk.io/store/v2/commitment/types"
"cosmossdk.io/store/v2"
)

var _ commitmenttypes.Tree = (*IavlTree)(nil)
var _ store.Tree = (*IavlTree)(nil)

// IavlTree is a wrapper around iavl.MutableTree.
type IavlTree struct {
Expand All @@ -27,8 +27,8 @@ func NewIavlTree(db dbm.DB, logger log.Logger, cfg *Config) *IavlTree {
}

// WriteBatch writes a batch of key-value pairs to the database.
func (t *IavlTree) WriteBatch(batch *commitmenttypes.Batch) error {
for _, kv := range batch.Pairs {
func (t *IavlTree) WriteBatch(cs *store.ChangeSet) error {
for _, kv := range cs.Pairs {
if kv.Value == nil {
_, res, err := t.tree.Remove(kv.Key)
if err != nil {
Expand Down
33 changes: 0 additions & 33 deletions store/commitment/types/batch.go

This file was deleted.

Loading

0 comments on commit 81cd74d

Please sign in to comment.