forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge PR cosmos#2458: Move linear to store
- Loading branch information
Showing
6 changed files
with
373 additions
and
423 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package store | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/cosmos/cosmos-sdk/codec" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
// Key for the length of the list | ||
func LengthKey() []byte { | ||
return []byte{0x00} | ||
} | ||
|
||
// Key for the elements of the list | ||
func ElemKey(index uint64) []byte { | ||
return append([]byte{0x01}, []byte(fmt.Sprintf("%020d", index))...) | ||
} | ||
|
||
// List defines an integer indexable mapper | ||
// It panics when the element type cannot be (un/)marshalled by the codec | ||
type List struct { | ||
cdc *codec.Codec | ||
store sdk.KVStore | ||
} | ||
|
||
// NewList constructs new List | ||
func NewList(cdc *codec.Codec, store sdk.KVStore) List { | ||
return List{ | ||
cdc: cdc, | ||
store: store, | ||
} | ||
} | ||
|
||
// Len() returns the length of the list | ||
// The length is only increased by Push() and not decreased | ||
// List dosen't check if an index is in bounds | ||
// The user should check Len() before doing any actions | ||
func (m List) Len() (res uint64) { | ||
bz := m.store.Get(LengthKey()) | ||
if bz == nil { | ||
return 0 | ||
} | ||
m.cdc.MustUnmarshalBinary(bz, &res) | ||
return | ||
} | ||
|
||
// Get() returns the element by its index | ||
func (m List) Get(index uint64, ptr interface{}) error { | ||
bz := m.store.Get(ElemKey(index)) | ||
return m.cdc.UnmarshalBinary(bz, ptr) | ||
} | ||
|
||
// 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 | ||
func (m List) Set(index uint64, value interface{}) { | ||
bz := m.cdc.MustMarshalBinary(value) | ||
m.store.Set(ElemKey(index), bz) | ||
} | ||
|
||
// Delete() deletes the element in the given position | ||
// Other elements' indices are preserved after deletion | ||
// Panics when the index is out of range | ||
func (m List) Delete(index uint64) { | ||
m.store.Delete(ElemKey(index)) | ||
} | ||
|
||
// Push() inserts the element to the end of the list | ||
// It will increase the length when it is called | ||
func (m List) Push(value interface{}) { | ||
length := m.Len() | ||
m.Set(length, value) | ||
m.store.Set(LengthKey(), m.cdc.MustMarshalBinary(length+1)) | ||
} | ||
|
||
// 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. | ||
func (m List) Iterate(ptr interface{}, fn func(uint64) bool) { | ||
iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) | ||
for ; iter.Valid(); iter.Next() { | ||
v := iter.Value() | ||
m.cdc.MustUnmarshalBinary(v, ptr) | ||
k := iter.Key() | ||
s := string(k[len(k)-20:]) | ||
index, err := strconv.ParseUint(s, 10, 64) | ||
if err != nil { | ||
panic(err) | ||
} | ||
if fn(index) { | ||
break | ||
} | ||
} | ||
|
||
iter.Close() | ||
} | ||
|
||
func subspace(prefix []byte) (start, end []byte) { | ||
end = make([]byte, len(prefix)) | ||
copy(end, prefix) | ||
end[len(end)-1]++ | ||
return prefix, end | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package store | ||
|
||
import ( | ||
"math/rand" | ||
"testing" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestList(t *testing.T) { | ||
key := sdk.NewKVStoreKey("test") | ||
ctx, cdc := defaultComponents(key) | ||
store := ctx.KVStore(key) | ||
lm := NewList(cdc, store) | ||
|
||
val := S{1, true} | ||
var res S | ||
|
||
lm.Push(val) | ||
require.Equal(t, uint64(1), lm.Len()) | ||
lm.Get(uint64(0), &res) | ||
require.Equal(t, val, res) | ||
|
||
val = S{2, false} | ||
lm.Set(uint64(0), val) | ||
lm.Get(uint64(0), &res) | ||
require.Equal(t, val, res) | ||
|
||
val = S{100, false} | ||
lm.Push(val) | ||
require.Equal(t, uint64(2), lm.Len()) | ||
lm.Get(uint64(1), &res) | ||
require.Equal(t, val, res) | ||
|
||
lm.Delete(uint64(1)) | ||
require.Equal(t, uint64(2), lm.Len()) | ||
|
||
lm.Iterate(&res, func(index uint64) (brk bool) { | ||
var temp S | ||
lm.Get(index, &temp) | ||
require.Equal(t, temp, res) | ||
|
||
require.True(t, index != 1) | ||
return | ||
}) | ||
|
||
lm.Iterate(&res, func(index uint64) (brk bool) { | ||
lm.Set(index, S{res.I + 1, !res.B}) | ||
return | ||
}) | ||
|
||
lm.Get(uint64(0), &res) | ||
require.Equal(t, S{3, true}, res) | ||
} | ||
|
||
func TestListRandom(t *testing.T) { | ||
key := sdk.NewKVStoreKey("test") | ||
ctx, cdc := defaultComponents(key) | ||
store := ctx.KVStore(key) | ||
list := NewList(cdc, store) | ||
mocklist := []uint32{} | ||
|
||
for i := 0; i < 100; i++ { | ||
item := rand.Uint32() | ||
list.Push(item) | ||
mocklist = append(mocklist, item) | ||
} | ||
|
||
for k, v := range mocklist { | ||
var i uint32 | ||
require.NotPanics(t, func() { list.Get(uint64(k), &i) }) | ||
require.Equal(t, v, i) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package store | ||
|
||
import ( | ||
"github.com/cosmos/cosmos-sdk/codec" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
// Key for the top element position in the queue | ||
func TopKey() []byte { | ||
return []byte{0x02} | ||
} | ||
|
||
// Queue is a List wrapper that provides queue-like functions | ||
// It panics when the element type cannot be (un/)marshalled by the codec | ||
type Queue struct { | ||
List List | ||
} | ||
|
||
// NewQueue constructs new Queue | ||
func NewQueue(cdc *codec.Codec, store sdk.KVStore) Queue { | ||
return Queue{NewList(cdc, store)} | ||
} | ||
|
||
func (m Queue) getTop() (res uint64) { | ||
bz := m.List.store.Get(TopKey()) | ||
if bz == nil { | ||
return 0 | ||
} | ||
|
||
m.List.cdc.MustUnmarshalBinary(bz, &res) | ||
return | ||
} | ||
|
||
func (m Queue) setTop(top uint64) { | ||
bz := m.List.cdc.MustMarshalBinary(top) | ||
m.List.store.Set(TopKey(), bz) | ||
} | ||
|
||
// Push() inserts the elements to the rear of the queue | ||
func (m Queue) Push(value interface{}) { | ||
m.List.Push(value) | ||
} | ||
|
||
// 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 | ||
func (m Queue) Peek(ptr interface{}) error { | ||
top := m.getTop() | ||
return m.List.Get(top, ptr) | ||
} | ||
|
||
// Pop() returns the element at the front of the queue and removes it | ||
func (m Queue) Pop() { | ||
top := m.getTop() | ||
m.List.Delete(top) | ||
m.setTop(top + 1) | ||
} | ||
|
||
// IsEmpty() checks if the queue is empty | ||
func (m Queue) IsEmpty() bool { | ||
top := m.getTop() | ||
length := m.List.Len() | ||
return top >= length | ||
} | ||
|
||
// 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 | ||
func (m Queue) Flush(ptr interface{}, fn func() bool) { | ||
top := m.getTop() | ||
length := m.List.Len() | ||
|
||
var i uint64 | ||
for i = top; i < length; i++ { | ||
err := m.List.Get(i, ptr) | ||
if err != nil { | ||
// TODO: Handle with #870 | ||
panic(err) | ||
} | ||
m.List.Delete(i) | ||
if fn() { | ||
break | ||
} | ||
} | ||
m.setTop(i) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package store | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
dbm "github.com/tendermint/tendermint/libs/db" | ||
"github.com/tendermint/tendermint/libs/log" | ||
|
||
abci "github.com/tendermint/tendermint/abci/types" | ||
|
||
"github.com/cosmos/cosmos-sdk/codec" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
type S struct { | ||
I uint64 | ||
B bool | ||
} | ||
|
||
func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { | ||
db := dbm.NewMemDB() | ||
cms := NewCommitMultiStore(db) | ||
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) | ||
cms.LoadLatestVersion() | ||
ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) | ||
cdc := codec.New() | ||
return ctx, cdc | ||
} | ||
|
||
func TestQueue(t *testing.T) { | ||
key := sdk.NewKVStoreKey("test") | ||
ctx, cdc := defaultComponents(key) | ||
store := ctx.KVStore(key) | ||
|
||
qm := NewQueue(cdc, store) | ||
|
||
val := S{1, true} | ||
var res S | ||
|
||
qm.Push(val) | ||
qm.Peek(&res) | ||
require.Equal(t, val, res) | ||
|
||
qm.Pop() | ||
empty := qm.IsEmpty() | ||
|
||
require.True(t, empty) | ||
require.NotNil(t, qm.Peek(&res)) | ||
|
||
qm.Push(S{1, true}) | ||
qm.Push(S{2, true}) | ||
qm.Push(S{3, true}) | ||
qm.Flush(&res, func() (brk bool) { | ||
if res.I == 3 { | ||
brk = true | ||
} | ||
return | ||
}) | ||
|
||
require.False(t, qm.IsEmpty()) | ||
|
||
qm.Pop() | ||
require.True(t, qm.IsEmpty()) | ||
} | ||
|
||
func TestKeys(t *testing.T) { | ||
key := sdk.NewKVStoreKey("test") | ||
ctx, cdc := defaultComponents(key) | ||
store := ctx.KVStore(key) | ||
queue := NewQueue(cdc, store) | ||
|
||
for i := 0; i < 10; i++ { | ||
queue.Push(i) | ||
} | ||
|
||
var len uint64 | ||
var top uint64 | ||
var expected int | ||
var actual int | ||
|
||
// Checking keys.LengthKey | ||
err := cdc.UnmarshalBinary(store.Get(LengthKey()), &len) | ||
require.Nil(t, err) | ||
require.Equal(t, len, queue.List.Len()) | ||
|
||
// Checking keys.ElemKey | ||
for i := 0; i < 10; i++ { | ||
queue.List.Get(uint64(i), &expected) | ||
bz := store.Get(ElemKey(uint64(i))) | ||
err = cdc.UnmarshalBinary(bz, &actual) | ||
require.Nil(t, err) | ||
require.Equal(t, expected, actual) | ||
} | ||
|
||
queue.Pop() | ||
|
||
err = cdc.UnmarshalBinary(store.Get(TopKey()), &top) | ||
require.Nil(t, err) | ||
require.Equal(t, top, queue.getTop()) | ||
} |
Oops, something went wrong.