diff --git a/mock/store.go b/mock/store.go new file mode 100644 index 0000000000..329eb250bb --- /dev/null +++ b/mock/store.go @@ -0,0 +1,112 @@ +package mock + +import ( + dbm "github.com/tendermint/tmlibs/db" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type multiStore struct { + kv map[sdk.StoreKey]kvStore +} + +func (ms multiStore) CacheMultiStore() sdk.CacheMultiStore { + panic("not implemented") +} + +func (ms multiStore) CacheWrap() sdk.CacheWrap { + panic("not implemented") +} + +func (ms multiStore) Commit() sdk.CommitID { + panic("not implemented") +} + +func (ms multiStore) LastCommitID() sdk.CommitID { + panic("not implemented") +} + +func (ms multiStore) GetCommitKVStore(key sdk.StoreKey) sdk.CommitKVStore { + panic("not implemented") +} + +func (ms multiStore) GetCommitStore(key sdk.StoreKey) sdk.CommitStore { + panic("not implemented") +} + +func (ms multiStore) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) { + ms.kv[key] = kvStore{store: make(map[string][]byte)} +} + +func (ms multiStore) LoadLatestVersion() error { + return nil +} + +func (ms multiStore) LoadVersion(ver int64) error { + panic("not implemented") +} + +func (ms multiStore) GetKVStore(key sdk.StoreKey) sdk.KVStore { + return ms.kv[key] +} + +func (ms multiStore) GetStore(key sdk.StoreKey) sdk.Store { + panic("not implemented") +} + +func (ms multiStore) GetStoreType() sdk.StoreType { + panic("not implemented") +} + +type kvStore struct { + store map[string][]byte +} + +func (kv kvStore) CacheWrap() sdk.CacheWrap { + panic("not implemented") +} + +func (kv kvStore) GetStoreType() sdk.StoreType { + panic("not implemented") +} + +func (kv kvStore) Get(key []byte) []byte { + v, ok := kv.store[string(key)] + if !ok { + return nil + } + return v +} + +func (kv kvStore) Has(key []byte) bool { + _, ok := kv.store[string(key)] + return ok +} + +func (kv kvStore) Set(key, value []byte) { + kv.store[string(key)] = value +} + +func (kv kvStore) Delete(key []byte) { + delete(kv.store, string(key)) +} + +func (kv kvStore) Iterator(start, end []byte) sdk.Iterator { + panic("not implemented") +} + +func (kv kvStore) ReverseIterator(start, end []byte) sdk.Iterator { + panic("not implemented") +} + +func (kv kvStore) SubspaceIterator(prefix []byte) sdk.Iterator { + panic("not implemented") +} + +func (kv kvStore) ReverseSubspaceIterator(prefix []byte) sdk.Iterator { + panic("not implemented") +} + +func NewCommitMultiStore(db dbm.DB) sdk.CommitMultiStore { + return multiStore{kv: make(map[sdk.StoreKey]kvStore)} +} diff --git a/mock/store_test.go b/mock/store_test.go new file mode 100644 index 0000000000..e920609476 --- /dev/null +++ b/mock/store_test.go @@ -0,0 +1,33 @@ +package mock + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbm "github.com/tendermint/tmlibs/db" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestStore(t *testing.T) { + db := dbm.NewMemDB() + cms := NewCommitMultiStore(db) + + key := sdk.NewKVStoreKey("test") + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + err := cms.LoadLatestVersion() + assert.Nil(t, err) + + store := cms.GetKVStore(key) + assert.NotNil(t, store) + + k := []byte("hello") + v := []byte("world") + assert.False(t, store.Has(k)) + store.Set(k, v) + assert.True(t, store.Has(k)) + assert.Equal(t, v, store.Get(k)) + store.Delete(k) + assert.False(t, store.Has(k)) +} diff --git a/types/lib/mapper.go b/types/lib/mapper.go new file mode 100644 index 0000000000..ca90200773 --- /dev/null +++ b/types/lib/mapper.go @@ -0,0 +1,286 @@ +package lib + +import ( + "fmt" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +// Mapper defines a primitive mapper type +type Mapper struct { + key sdk.StoreKey + cdc *wire.Codec + prefix string +} + +// ListMapper is a Mapper interface that provides list-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type ListMapper interface { + + // Len() returns the length of the list + // The length is only increased by Push() and not decreased + // ListMapper dosen't check if an index is in bounds + // The user should check Len() before doing any actions + Len(sdk.Context) uint64 + + // Get() returns the element by its index + Get(sdk.Context, uint64, interface{}) error + + // Set() stores the element to the given position + // Setting element out of range will break length counting + // Use Push() instead of Set() to append a new element + Set(sdk.Context, uint64, interface{}) + + // Delete() deletes the element in the given position + // Other elements' indices are preserved after deletion + // Panics when the index is out of range + Delete(sdk.Context, uint64) + + // Push() inserts the element to the end of the list + // It will increase the length when it is called + Push(sdk.Context, interface{}) + + // Iterate*() is used to iterate over all existing elements in the list + // Return true in the continuation to break + // The second element of the continuation will indicate the position of the element + // Using it with Get() will return the same one with the provided element + + // CONTRACT: No writes may happen within a domain while iterating over it. + IterateRead(sdk.Context, interface{}, func(sdk.Context, uint64) bool) + + // IterateWrite() is safe to write over the domain + IterateWrite(sdk.Context, interface{}, func(sdk.Context, uint64) bool) + + // Key for the length of the list + LengthKey() []byte + + // Key for getting elements + ElemKey(uint64) []byte +} + +// NewListMapper constructs new ListMapper +func NewListMapper(cdc *wire.Codec, key sdk.StoreKey, prefix string) ListMapper { + return Mapper{ + key: key, + cdc: cdc, + prefix: prefix, + } +} + +// Len implements ListMapper +func (m Mapper) Len(ctx sdk.Context) uint64 { + store := ctx.KVStore(m.key) + bz := store.Get(m.LengthKey()) + if bz == nil { + zero, err := m.cdc.MarshalBinary(0) + if err != nil { + panic(err) + } + store.Set(m.LengthKey(), zero) + return 0 + } + var res uint64 + if err := m.cdc.UnmarshalBinary(bz, &res); err != nil { + panic(err) + } + return res +} + +// Get implements ListMapper +func (m Mapper) Get(ctx sdk.Context, index uint64, ptr interface{}) error { + store := ctx.KVStore(m.key) + bz := store.Get(m.ElemKey(index)) + return m.cdc.UnmarshalBinary(bz, ptr) +} + +// Set implements ListMapper +func (m Mapper) Set(ctx sdk.Context, index uint64, value interface{}) { + store := ctx.KVStore(m.key) + bz, err := m.cdc.MarshalBinary(value) + if err != nil { + panic(err) + } + store.Set(m.ElemKey(index), bz) +} + +// Delete implements ListMapper +func (m Mapper) Delete(ctx sdk.Context, index uint64) { + store := ctx.KVStore(m.key) + store.Delete(m.ElemKey(index)) +} + +// Push implements ListMapper +func (m Mapper) Push(ctx sdk.Context, value interface{}) { + length := m.Len(ctx) + m.Set(ctx, length, value) + + store := ctx.KVStore(m.key) + store.Set(m.LengthKey(), marshalUint64(m.cdc, length+1)) +} + +// IterateRead implements ListMapper +func (m Mapper) IterateRead(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, uint64) bool) { + store := ctx.KVStore(m.key) + start, end := subspace([]byte(fmt.Sprintf("%s/elem/", m.prefix))) + iter := store.Iterator(start, end) + for ; iter.Valid(); iter.Next() { + v := iter.Value() + if err := m.cdc.UnmarshalBinary(v, ptr); err != nil { + panic(err) + } + s := string(iter.Key()[len(m.prefix)+6:]) + index, err := strconv.ParseUint(s, 10, 64) + if err != nil { + panic(err) + } + if fn(ctx, index) { + break + } + } + + iter.Close() +} + +// IterateWrite implements ListMapper +func (m Mapper) IterateWrite(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, uint64) bool) { + length := m.Len(ctx) + + for i := uint64(0); i < length; i++ { + if err := m.Get(ctx, i, ptr); err != nil { + continue + } + if fn(ctx, i) { + break + } + } +} + +// LengthKey implements ListMapper +func (m Mapper) LengthKey() []byte { + return []byte(fmt.Sprintf("%s/length", m.prefix)) +} + +// ElemKey implements ListMapper +func (m Mapper) ElemKey(i uint64) []byte { + return []byte(fmt.Sprintf("%s/elem/%020d", m.prefix, i)) +} + +// QueueMapper is a Mapper interface that provides queue-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type QueueMapper interface { + // Push() inserts the elements to the rear of the queue + Push(sdk.Context, interface{}) + + // Popping/Peeking on an empty queue will cause panic + // The user should check IsEmpty() before doing any actions + + // Peek() returns the element at the front of the queue without removing it + Peek(sdk.Context, interface{}) error + + // Pop() returns the element at the front of the queue and removes it + Pop(sdk.Context) + + // IsEmpty() checks if the queue is empty + IsEmpty(sdk.Context) bool + + // Flush() removes elements it processed + // Return true in the continuation to break + // The interface{} is unmarshalled before the continuation is called + // Starts from the top(head) of the queue + // CONTRACT: Pop() or Push() should not be performed while flushing + Flush(sdk.Context, interface{}, func(sdk.Context) bool) + + // Key for the index of top element + TopKey() []byte +} + +// NewQueueMapper constructs new QueueMapper +func NewQueueMapper(cdc *wire.Codec, key sdk.StoreKey, prefix string) QueueMapper { + return Mapper{ + key: key, + cdc: cdc, + prefix: prefix, + } +} + +func (m Mapper) getTop(store sdk.KVStore) (res uint64) { + bz := store.Get(m.TopKey()) + if bz == nil { + store.Set(m.TopKey(), marshalUint64(m.cdc, 0)) + return 0 + } + + if err := m.cdc.UnmarshalBinary(bz, &res); err != nil { + panic(err) + } + + return +} + +func (m Mapper) setTop(store sdk.KVStore, top uint64) { + bz := marshalUint64(m.cdc, top) + store.Set(m.TopKey(), bz) +} + +// Peek implements QueueMapper +func (m Mapper) Peek(ctx sdk.Context, ptr interface{}) error { + store := ctx.KVStore(m.key) + top := m.getTop(store) + return m.Get(ctx, top, ptr) +} + +// Pop implements QueueMapper +func (m Mapper) Pop(ctx sdk.Context) { + store := ctx.KVStore(m.key) + top := m.getTop(store) + m.Delete(ctx, top) + m.setTop(store, top+1) +} + +// IsEmpty implements QueueMapper +func (m Mapper) IsEmpty(ctx sdk.Context) bool { + store := ctx.KVStore(m.key) + top := m.getTop(store) + length := m.Len(ctx) + return top >= length +} + +// Flush implements QueueMapper +func (m Mapper) Flush(ctx sdk.Context, ptr interface{}, fn func(sdk.Context) bool) { + store := ctx.KVStore(m.key) + top := m.getTop(store) + length := m.Len(ctx) + + var i uint64 + for i = top; i < length; i++ { + m.Get(ctx, i, ptr) + m.Delete(ctx, i) + if fn(ctx) { + break + } + } + + m.setTop(store, i) +} + +// TopKey implements QueueMapper +func (m Mapper) TopKey() []byte { + return []byte(fmt.Sprintf("%s/top", m.prefix)) +} + +func marshalUint64(cdc *wire.Codec, i uint64) []byte { + bz, err := cdc.MarshalBinary(i) + if err != nil { + panic(err) + } + return bz +} + +func subspace(prefix []byte) (start, end []byte) { + end = make([]byte, len(prefix)) + copy(end, prefix) + end[len(end)-1]++ + return prefix, end +} diff --git a/types/lib/mapper_test.go b/types/lib/mapper_test.go new file mode 100644 index 0000000000..1058fed6ed --- /dev/null +++ b/types/lib/mapper_test.go @@ -0,0 +1,109 @@ +package lib + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbm "github.com/tendermint/tmlibs/db" + + abci "github.com/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +type S struct { + I uint64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil) + cdc := wire.NewCodec() + return ctx, cdc +} + +func TestListMapper(t *testing.T) { + key := sdk.NewKVStoreKey("list") + ctx, cdc := defaultComponents(key) + lm := NewListMapper(cdc, key, "data") + + val := S{1, true} + var res S + + lm.Push(ctx, val) + assert.Equal(t, uint64(1), lm.Len(ctx)) + lm.Get(ctx, uint64(0), &res) + assert.Equal(t, val, res) + + val = S{2, false} + lm.Set(ctx, uint64(0), val) + lm.Get(ctx, uint64(0), &res) + assert.Equal(t, val, res) + + val = S{100, false} + lm.Push(ctx, val) + assert.Equal(t, uint64(2), lm.Len(ctx)) + lm.Get(ctx, uint64(1), &res) + assert.Equal(t, val, res) + + lm.Delete(ctx, uint64(1)) + assert.Equal(t, uint64(2), lm.Len(ctx)) + + lm.IterateRead(ctx, &res, func(ctx sdk.Context, index uint64) (brk bool) { + var temp S + lm.Get(ctx, index, &temp) + assert.Equal(t, temp, res) + + assert.True(t, index != 1) + return + }) + + lm.IterateWrite(ctx, &res, func(ctx sdk.Context, index uint64) (brk bool) { + lm.Set(ctx, index, S{res.I + 1, !res.B}) + return + }) + + lm.Get(ctx, uint64(0), &res) + assert.Equal(t, S{3, true}, res) +} + +func TestQueueMapper(t *testing.T) { + key := sdk.NewKVStoreKey("queue") + ctx, cdc := defaultComponents(key) + qm := NewQueueMapper(cdc, key, "data") + + val := S{1, true} + var res S + + qm.Push(ctx, val) + qm.Peek(ctx, &res) + assert.Equal(t, val, res) + + qm.Pop(ctx) + empty := qm.IsEmpty(ctx) + + assert.True(t, empty) + assert.NotNil(t, qm.Peek(ctx, &res)) + + qm.Push(ctx, S{1, true}) + qm.Push(ctx, S{2, true}) + qm.Push(ctx, S{3, true}) + qm.Flush(ctx, &res, func(ctx sdk.Context) (brk bool) { + if res.I == 3 { + brk = true + } + return + }) + + assert.False(t, qm.IsEmpty(ctx)) + + qm.Pop(ctx) + assert.True(t, qm.IsEmpty(ctx)) +} diff --git a/types/stdlib/stdlib.go b/types/stdlib/stdlib.go deleted file mode 100644 index acb7863059..0000000000 --- a/types/stdlib/stdlib.go +++ /dev/null @@ -1,234 +0,0 @@ -package types - -import ( - "errors" - - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" -) - -// Solidity list like structure -type ListMapper interface { - Len(sdk.Context) int64 - Get(sdk.Context, int64, interface{}) - Set(sdk.Context, int64, interface{}) - Push(sdk.Context, interface{}) - Iterate(sdk.Context, interface{}, func(sdk.Context, int64)) -} - -type listMapper struct { - key sdk.StoreKey - cdc *wire.Codec - lk []byte -} - -func NewListMapper(cdc *wire.Codec, key sdk.StoreKey) ListMapper { - lk, err := cdc.MarshalBinary(int64(-1)) - if err != nil { - panic(err) - } - return listMapper{ - key: key, - cdc: cdc, - lk: lk, - } -} - -func (lm listMapper) Len(ctx sdk.Context) int64 { - store := ctx.KVStore(lm.key) - bz := store.Get(lm.lk) - if bz == nil { - zero, err := lm.cdc.MarshalBinary(0) - if err != nil { - panic(err) - } - store.Set(lm.lk, zero) - return 0 - } - var res int64 - if err := lm.cdc.UnmarshalBinary(bz, &res); err != nil { - panic(err) - } - return res -} - -func (lm listMapper) Get(ctx sdk.Context, index int64, ptr interface{}) { - if index < 0 { - panic(errors.New("")) - } - store := ctx.KVStore(lm.key) - bz := store.Get(marshalInt64(lm.cdc, index)) - if err := lm.cdc.UnmarshalBinary(bz, ptr); err != nil { - panic(err) - } -} - -func (lm listMapper) Set(ctx sdk.Context, index int64, value interface{}) { - if index < 0 { - panic(errors.New("")) - } - store := ctx.KVStore(lm.key) - bz, err := lm.cdc.MarshalBinary(value) - if err != nil { - panic(err) - } - store.Set(marshalInt64(lm.cdc, index), bz) -} - -func (lm listMapper) Push(ctx sdk.Context, value interface{}) { - length := lm.Len(ctx) - lm.Set(ctx, length, value) - - store := ctx.KVStore(lm.key) - store.Set(lm.lk, marshalInt64(lm.cdc, length+1)) -} - -func (lm listMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, int64)) { - length := lm.Len(ctx) - for i := int64(0); i < length; i++ { - lm.Get(ctx, i, ptr) - fn(ctx, i) - } -} - -// mapper interface for queue -type QueueMapper interface { - Push(sdk.Context, interface{}) - Peek(sdk.Context, interface{}) - Pop(sdk.Context) - IsEmpty(sdk.Context) bool - Iterate(sdk.Context, interface{}, func(sdk.Context) bool) -} - -type queueMapper struct { - key sdk.StoreKey - cdc *wire.Codec - ik []byte -} - -func NewQueueMapper(cdc *wire.Codec, key sdk.StoreKey) QueueMapper { - ik, err := cdc.MarshalBinary(int64(-1)) - if err != nil { - panic(err) - } - return queueMapper{ - key: key, - cdc: cdc, - ik: ik, - } -} - -type queueInfo struct { - // begin <= elems < end - Begin int64 - End int64 -} - -func (info queueInfo) validateBasic() error { - if info.End < info.Begin || info.Begin < 0 || info.End < 0 { - return errors.New("") - } - return nil -} - -func (info queueInfo) isEmpty() bool { - return info.Begin == info.End -} - -func (qm queueMapper) getQueueInfo(store sdk.KVStore) queueInfo { - bz := store.Get(qm.ik) - if bz == nil { - store.Set(qm.ik, marshalQueueInfo(qm.cdc, queueInfo{0, 0})) - return queueInfo{0, 0} - } - var info queueInfo - if err := qm.cdc.UnmarshalBinary(bz, &info); err != nil { - panic(err) - } - if err := info.validateBasic(); err != nil { - panic(err) - } - return info -} - -func (qm queueMapper) setQueueInfo(store sdk.KVStore, info queueInfo) { - bz, err := qm.cdc.MarshalBinary(info) - if err != nil { - panic(err) - } - store.Set(qm.ik, bz) -} - -func (qm queueMapper) Push(ctx sdk.Context, value interface{}) { - store := ctx.KVStore(qm.key) - info := qm.getQueueInfo(store) - - bz, err := qm.cdc.MarshalBinary(value) - if err != nil { - panic(err) - } - store.Set(marshalInt64(qm.cdc, info.End), bz) - - info.End++ - qm.setQueueInfo(store, info) -} - -func (qm queueMapper) Peek(ctx sdk.Context, ptr interface{}) { - store := ctx.KVStore(qm.key) - info := qm.getQueueInfo(store) - bz := store.Get(marshalInt64(qm.cdc, info.Begin)) - if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil { - panic(err) - } -} - -func (qm queueMapper) Pop(ctx sdk.Context) { - store := ctx.KVStore(qm.key) - info := qm.getQueueInfo(store) - store.Delete(marshalInt64(qm.cdc, info.Begin)) - info.Begin++ - qm.setQueueInfo(store, info) -} - -func (qm queueMapper) IsEmpty(ctx sdk.Context) bool { - store := ctx.KVStore(qm.key) - info := qm.getQueueInfo(store) - return info.isEmpty() -} - -func (qm queueMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context) bool) { - store := ctx.KVStore(qm.key) - info := qm.getQueueInfo(store) - - var i int64 - for i = info.Begin; i < info.End; i++ { - key := marshalInt64(qm.cdc, i) - bz := store.Get(key) - if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil { - panic(err) - } - store.Delete(key) - if fn(ctx) { - break - } - } - - info.Begin = i - qm.setQueueInfo(store, info) -} - -func marshalQueueInfo(cdc *wire.Codec, info queueInfo) []byte { - bz, err := cdc.MarshalBinary(info) - if err != nil { - panic(err) - } - return bz -} - -func marshalInt64(cdc *wire.Codec, i int64) []byte { - bz, err := cdc.MarshalBinary(i) - if err != nil { - panic(err) - } - return bz -} diff --git a/types/stdlib/stdlib_test.go b/types/stdlib/stdlib_test.go deleted file mode 100644 index 7c871ccf3c..0000000000 --- a/types/stdlib/stdlib_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - dbm "github.com/tendermint/tmlibs/db" - - abci "github.com/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" -) - -type S struct { - I int64 - B bool -} - -func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil) - cdc := wire.NewCodec() - return ctx, cdc -} - -func TestListMapper(t *testing.T) { - key := sdk.NewKVStoreKey("list") - ctx, cdc := defaultComponents(key) - lm := NewListMapper(cdc, key) - - val := S{1, true} - var res S - - lm.Push(ctx, val) - assert.Equal(t, int64(1), lm.Len(ctx)) - lm.Get(ctx, int64(0), &res) - assert.Equal(t, val, res) - - val = S{2, false} - lm.Set(ctx, int64(0), val) - lm.Get(ctx, int64(0), &res) - assert.Equal(t, val, res) -} - -func TestQueueMapper(t *testing.T) { - key := sdk.NewKVStoreKey("queue") - ctx, cdc := defaultComponents(key) - qm := NewQueueMapper(cdc, key) - - val := S{1, true} - var res S - - qm.Push(ctx, val) - qm.Peek(ctx, &res) - assert.Equal(t, val, res) - - qm.Pop(ctx) - empty := qm.IsEmpty(ctx) - - assert.Equal(t, true, empty) - - assert.Panics(t, func() { qm.Peek(ctx, &res) }) -}