cosmos-sdk/store/iavl/store_test.go

580 lines
16 KiB
Go
Raw Normal View History

2019-02-01 17:03:09 -08:00
package iavl
import (
crand "crypto/rand"
2018-07-02 15:17:45 -07:00
"fmt"
"testing"
"github.com/cosmos/iavl"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tm-db"
2019-02-05 10:39:22 -08:00
"github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/types/kv"
)
var (
2020-01-22 11:52:56 -08:00
cacheSize = 100
treeData = map[string]string{
"hello": "goodbye",
"aloha": "shalom",
}
nMoreData = 0
)
func randBytes(numBytes int) []byte {
b := make([]byte, numBytes)
_, _ = crand.Read(b)
return b
}
// make a tree with data from above and save it
2019-02-01 17:03:09 -08:00
func newAlohaTree(t *testing.T, db dbm.DB) (*iavl.MutableTree, types.CommitID) {
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
for k, v := range treeData {
tree.Set([]byte(k), []byte(v))
}
for i := 0; i < nMoreData; i++ {
key := randBytes(12)
value := randBytes(50)
tree.Set(key, value)
}
hash, ver, err := tree.SaveVersion()
require.Nil(t, err)
return tree, types.CommitID{Version: ver, Hash: hash}
}
func TestGetImmutable(t *testing.T) {
db := dbm.NewMemDB()
tree, cID := newAlohaTree(t, db)
2020-06-22 13:31:33 -07:00
store := UnsafeNewStore(tree)
require.True(t, tree.Set([]byte("hello"), []byte("adios")))
hash, ver, err := tree.SaveVersion()
cID = types.CommitID{Version: ver, Hash: hash}
require.Nil(t, err)
_, err = store.GetImmutable(cID.Version + 1)
require.Error(t, err)
newStore, err := store.GetImmutable(cID.Version - 1)
require.NoError(t, err)
require.Equal(t, newStore.Get([]byte("hello")), []byte("goodbye"))
newStore, err = store.GetImmutable(cID.Version)
require.NoError(t, err)
require.Equal(t, newStore.Get([]byte("hello")), []byte("adios"))
res := newStore.Query(abci.RequestQuery{Data: []byte("hello"), Height: cID.Version, Path: "/key", Prove: true})
require.Equal(t, res.Value, []byte("adios"))
require.NotNil(t, res.ProofOps)
require.Panics(t, func() { newStore.Set(nil, nil) })
require.Panics(t, func() { newStore.Delete(nil) })
require.Panics(t, func() { newStore.Commit() })
}
func TestTestGetImmutableIterator(t *testing.T) {
db := dbm.NewMemDB()
tree, cID := newAlohaTree(t, db)
2020-06-22 13:31:33 -07:00
store := UnsafeNewStore(tree)
newStore, err := store.GetImmutable(cID.Version)
require.NoError(t, err)
iter := newStore.Iterator([]byte("aloha"), []byte("hellz"))
expected := []string{"aloha", "hello"}
var i int
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, treeData[expectedKey])
i++
}
require.Equal(t, len(expected), i)
}
func TestIAVLStoreGetSetHasDelete(t *testing.T) {
db := dbm.NewMemDB()
tree, _ := newAlohaTree(t, db)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
key := "hello"
exists := iavlStore.Has([]byte(key))
require.True(t, exists)
value := iavlStore.Get([]byte(key))
require.EqualValues(t, value, treeData[key])
value2 := "notgoodbye"
iavlStore.Set([]byte(key), []byte(value2))
value = iavlStore.Get([]byte(key))
require.EqualValues(t, value, value2)
iavlStore.Delete([]byte(key))
exists = iavlStore.Has([]byte(key))
require.False(t, exists)
}
func TestIAVLStoreNoNilSet(t *testing.T) {
db := dbm.NewMemDB()
tree, _ := newAlohaTree(t, db)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
require.Panics(t, func() { iavlStore.Set(nil, []byte("value")) }, "setting a nil key should panic")
require.Panics(t, func() { iavlStore.Set([]byte(""), []byte("value")) }, "setting an empty key should panic")
require.Panics(t, func() { iavlStore.Set([]byte("key"), nil) }, "setting a nil value should panic")
}
func TestIAVLIterator(t *testing.T) {
db := dbm.NewMemDB()
tree, _ := newAlohaTree(t, db)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
iter := iavlStore.Iterator([]byte("aloha"), []byte("hellz"))
expected := []string{"aloha", "hello"}
2018-04-05 12:54:30 -07:00
var i int
2018-03-30 13:36:04 -07:00
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, treeData[expectedKey])
2018-04-18 21:49:24 -07:00
i++
}
require.Equal(t, len(expected), i)
2018-03-30 13:36:04 -07:00
iter = iavlStore.Iterator([]byte("golang"), []byte("rocks"))
expected = []string{"hello"}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, treeData[expectedKey])
2018-04-18 21:49:24 -07:00
i++
2018-03-31 15:02:29 -07:00
}
require.Equal(t, len(expected), i)
2018-03-31 15:02:29 -07:00
iter = iavlStore.Iterator(nil, []byte("golang"))
expected = []string{"aloha"}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, treeData[expectedKey])
2018-04-18 21:49:24 -07:00
i++
2018-03-31 15:02:29 -07:00
}
require.Equal(t, len(expected), i)
2018-03-31 15:02:29 -07:00
iter = iavlStore.Iterator(nil, []byte("shalom"))
expected = []string{"aloha", "hello"}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, treeData[expectedKey])
2018-04-18 21:49:24 -07:00
i++
2018-03-31 15:02:29 -07:00
}
require.Equal(t, len(expected), i)
2018-03-31 15:02:29 -07:00
iter = iavlStore.Iterator(nil, nil)
expected = []string{"aloha", "hello"}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, treeData[expectedKey])
2018-04-18 21:49:24 -07:00
i++
2018-03-30 13:36:04 -07:00
}
require.Equal(t, len(expected), i)
2018-03-31 10:30:25 -07:00
iter = iavlStore.Iterator([]byte("golang"), nil)
expected = []string{"hello"}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, treeData[expectedKey])
2018-04-18 21:49:24 -07:00
i++
2018-03-31 10:30:25 -07:00
}
require.Equal(t, len(expected), i)
}
2018-01-30 10:59:05 -08:00
func TestIAVLReverseIterator(t *testing.T) {
2018-03-19 13:04:52 -07:00
db := dbm.NewMemDB()
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
iavlStore.Set([]byte{0x00}, []byte("0"))
iavlStore.Set([]byte{0x00, 0x00}, []byte("0 0"))
iavlStore.Set([]byte{0x00, 0x01}, []byte("0 1"))
iavlStore.Set([]byte{0x00, 0x02}, []byte("0 2"))
iavlStore.Set([]byte{0x01}, []byte("1"))
var testReverseIterator = func(t *testing.T, start []byte, end []byte, expected []string) {
iter := iavlStore.ReverseIterator(start, end)
var i int
for i = 0; iter.Valid(); iter.Next() {
expectedValue := expected[i]
value := iter.Value()
require.EqualValues(t, string(value), expectedValue)
i++
}
require.Equal(t, len(expected), i)
}
testReverseIterator(t, nil, nil, []string{"1", "0 2", "0 1", "0 0", "0"})
testReverseIterator(t, []byte{0x00}, nil, []string{"1", "0 2", "0 1", "0 0", "0"})
testReverseIterator(t, []byte{0x00}, []byte{0x00, 0x01}, []string{"0 0", "0"})
testReverseIterator(t, []byte{0x00}, []byte{0x01}, []string{"0 2", "0 1", "0 0", "0"})
testReverseIterator(t, []byte{0x00, 0x01}, []byte{0x01}, []string{"0 2", "0 1"})
testReverseIterator(t, nil, []byte{0x01}, []string{"0 2", "0 1", "0 0", "0"})
}
func TestIAVLPrefixIterator(t *testing.T) {
db := dbm.NewMemDB()
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
2018-03-19 13:04:52 -07:00
iavlStore.Set([]byte("test1"), []byte("test1"))
iavlStore.Set([]byte("test2"), []byte("test2"))
iavlStore.Set([]byte("test3"), []byte("test3"))
2018-03-30 13:36:04 -07:00
iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(0)}, []byte("test4"))
iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(1)}, []byte("test4"))
iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(255)}, []byte("test4"))
2018-03-31 15:26:00 -07:00
iavlStore.Set([]byte{byte(255), byte(255), byte(0)}, []byte("test4"))
iavlStore.Set([]byte{byte(255), byte(255), byte(1)}, []byte("test4"))
iavlStore.Set([]byte{byte(255), byte(255), byte(255)}, []byte("test4"))
var i int
2018-03-19 13:04:52 -07:00
2019-02-01 17:03:09 -08:00
iter := types.KVStorePrefixIterator(iavlStore, []byte("test"))
2018-03-19 13:04:52 -07:00
expected := []string{"test1", "test2", "test3"}
2018-03-30 13:36:04 -07:00
for i = 0; iter.Valid(); iter.Next() {
2018-03-19 13:04:52 -07:00
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, expectedKey)
2018-04-18 21:49:24 -07:00
i++
2018-03-19 13:04:52 -07:00
}
iter.Close()
require.Equal(t, len(expected), i)
2018-03-30 13:36:04 -07:00
2019-02-01 17:03:09 -08:00
iter = types.KVStorePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)})
2018-03-30 13:36:04 -07:00
expected2 := [][]byte{
{byte(55), byte(255), byte(255), byte(0)},
{byte(55), byte(255), byte(255), byte(1)},
{byte(55), byte(255), byte(255), byte(255)},
2018-03-30 13:36:04 -07:00
}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected2[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, []byte("test4"))
2018-04-18 21:49:24 -07:00
i++
2018-03-30 13:36:04 -07:00
}
iter.Close()
require.Equal(t, len(expected), i)
2018-03-30 13:36:04 -07:00
2019-02-01 17:03:09 -08:00
iter = types.KVStorePrefixIterator(iavlStore, []byte{byte(255), byte(255)})
2018-03-30 13:36:04 -07:00
expected2 = [][]byte{
{byte(255), byte(255), byte(0)},
{byte(255), byte(255), byte(1)},
{byte(255), byte(255), byte(255)},
2018-03-30 13:36:04 -07:00
}
for i = 0; iter.Valid(); iter.Next() {
2018-04-01 09:00:28 -07:00
expectedKey := expected2[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, []byte("test4"))
2018-04-18 21:49:24 -07:00
i++
2018-04-01 09:00:28 -07:00
}
iter.Close()
require.Equal(t, len(expected), i)
2018-04-01 09:00:28 -07:00
}
func TestIAVLReversePrefixIterator(t *testing.T) {
2018-04-01 09:00:28 -07:00
db := dbm.NewMemDB()
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
2018-04-01 09:00:28 -07:00
iavlStore.Set([]byte("test1"), []byte("test1"))
iavlStore.Set([]byte("test2"), []byte("test2"))
iavlStore.Set([]byte("test3"), []byte("test3"))
iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(0)}, []byte("test4"))
iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(1)}, []byte("test4"))
iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(255)}, []byte("test4"))
iavlStore.Set([]byte{byte(255), byte(255), byte(0)}, []byte("test4"))
iavlStore.Set([]byte{byte(255), byte(255), byte(1)}, []byte("test4"))
iavlStore.Set([]byte{byte(255), byte(255), byte(255)}, []byte("test4"))
var i int
2018-04-01 09:00:28 -07:00
2019-02-01 17:03:09 -08:00
iter := types.KVStoreReversePrefixIterator(iavlStore, []byte("test"))
2018-04-01 09:00:28 -07:00
expected := []string{"test3", "test2", "test1"}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, expectedKey)
2018-04-18 21:49:24 -07:00
i++
2018-04-01 09:00:28 -07:00
}
require.Equal(t, len(expected), i)
2018-04-01 09:00:28 -07:00
2019-02-01 17:03:09 -08:00
iter = types.KVStoreReversePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)})
2018-04-01 09:00:28 -07:00
expected2 := [][]byte{
{byte(55), byte(255), byte(255), byte(255)},
{byte(55), byte(255), byte(255), byte(1)},
{byte(55), byte(255), byte(255), byte(0)},
2018-04-01 09:00:28 -07:00
}
for i = 0; iter.Valid(); iter.Next() {
expectedKey := expected2[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, []byte("test4"))
2018-04-18 21:49:24 -07:00
i++
2018-04-01 09:00:28 -07:00
}
require.Equal(t, len(expected), i)
2018-04-01 09:00:28 -07:00
2019-02-01 17:03:09 -08:00
iter = types.KVStoreReversePrefixIterator(iavlStore, []byte{byte(255), byte(255)})
2018-04-01 09:00:28 -07:00
expected2 = [][]byte{
{byte(255), byte(255), byte(255)},
{byte(255), byte(255), byte(1)},
{byte(255), byte(255), byte(0)},
2018-04-01 09:00:28 -07:00
}
for i = 0; iter.Valid(); iter.Next() {
2018-03-30 13:36:04 -07:00
expectedKey := expected2[i]
key, value := iter.Key(), iter.Value()
require.EqualValues(t, key, expectedKey)
require.EqualValues(t, value, []byte("test4"))
2018-04-18 21:49:24 -07:00
i++
2018-03-30 13:36:04 -07:00
}
require.Equal(t, len(expected), i)
2018-03-19 13:04:52 -07:00
}
2019-02-01 17:03:09 -08:00
func nextVersion(iavl *Store) {
2018-07-02 15:17:45 -07:00
key := []byte(fmt.Sprintf("Key for tree: %d", iavl.LastCommitID().Version))
value := []byte(fmt.Sprintf("Value for tree: %d", iavl.LastCommitID().Version))
2018-06-26 16:26:24 -07:00
iavl.Set(key, value)
iavl.Commit()
}
2018-07-12 18:20:26 -07:00
2018-07-02 15:17:45 -07:00
func TestIAVLNoPrune(t *testing.T) {
db := dbm.NewMemDB()
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
2018-06-26 16:26:24 -07:00
nextVersion(iavlStore)
2018-07-02 15:17:45 -07:00
for i := 1; i < 100; i++ {
for j := 1; j <= i; j++ {
require.True(t, iavlStore.VersionExists(int64(j)),
"Missing version %d with latest version %d. Should be storing all versions",
j, i)
2018-06-26 16:26:24 -07:00
}
2018-06-26 16:26:24 -07:00
nextVersion(iavlStore)
}
2018-07-02 15:17:45 -07:00
}
2018-07-12 18:20:26 -07:00
2018-01-30 10:59:05 -08:00
func TestIAVLStoreQuery(t *testing.T) {
db := dbm.NewMemDB()
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
2018-01-30 10:59:05 -08:00
2018-05-08 13:32:41 -07:00
k1, v1 := []byte("key1"), []byte("val1")
k2, v2 := []byte("key2"), []byte("val2")
2018-05-08 13:15:35 -07:00
v3 := []byte("val3")
2018-05-08 13:32:41 -07:00
ksub := []byte("key")
KVs0 := kv.Pairs{}
KVs1 := kv.Pairs{
Pairs: []kv.Pair{
{Key: k1, Value: v1},
{Key: k2, Value: v2},
},
2018-05-08 13:32:41 -07:00
}
KVs2 := kv.Pairs{
Pairs: []kv.Pair{
{Key: k1, Value: v3},
{Key: k2, Value: v2},
},
2018-05-08 13:32:41 -07:00
}
valExpSubEmpty, err := KVs0.Marshal()
require.NoError(t, err)
valExpSub1, err := KVs1.Marshal()
require.NoError(t, err)
valExpSub2, err := KVs2.Marshal()
require.NoError(t, err)
2018-01-30 10:59:05 -08:00
cid := iavlStore.Commit()
ver := cid.Version
2018-05-08 13:15:35 -07:00
query := abci.RequestQuery{Path: "/key", Data: k1, Height: ver}
querySub := abci.RequestQuery{Path: "/subspace", Data: ksub, Height: ver}
2018-01-30 10:59:05 -08:00
2018-05-08 13:32:41 -07:00
// query subspace before anything set
qres := iavlStore.Query(querySub)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, valExpSubEmpty, qres.Value)
2018-05-08 13:32:41 -07:00
// set data
iavlStore.Set(k1, v1)
iavlStore.Set(k2, v2)
// set data without commit, doesn't show up
qres = iavlStore.Query(query)
require.Equal(t, uint32(0), qres.Code)
require.Nil(t, qres.Value)
2018-01-30 10:59:05 -08:00
// commit it, but still don't see on old version
cid = iavlStore.Commit()
qres = iavlStore.Query(query)
require.Equal(t, uint32(0), qres.Code)
require.Nil(t, qres.Value)
2018-01-30 10:59:05 -08:00
// but yes on the new version
query.Height = cid.Version
qres = iavlStore.Query(query)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, v1, qres.Value)
2018-05-08 13:15:35 -07:00
// and for the subspace
qres = iavlStore.Query(querySub)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, valExpSub1, qres.Value)
2018-01-30 10:59:05 -08:00
// modify
2018-05-08 13:15:35 -07:00
iavlStore.Set(k1, v3)
2018-01-30 10:59:05 -08:00
cid = iavlStore.Commit()
// query will return old values, as height is fixed
qres = iavlStore.Query(query)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, v1, qres.Value)
2018-05-08 13:15:35 -07:00
2018-01-30 10:59:05 -08:00
// update to latest in the query and we are happy
query.Height = cid.Version
qres = iavlStore.Query(query)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, v3, qres.Value)
2018-01-30 10:59:05 -08:00
query2 := abci.RequestQuery{Path: "/key", Data: k2, Height: cid.Version}
2018-07-26 18:24:18 -07:00
2018-01-30 10:59:05 -08:00
qres = iavlStore.Query(query2)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, v2, qres.Value)
2018-05-08 13:15:35 -07:00
// and for the subspace
qres = iavlStore.Query(querySub)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, valExpSub2, qres.Value)
2018-01-30 10:59:05 -08:00
// default (height 0) will show latest -1
query0 := abci.RequestQuery{Path: "/key", Data: k1}
2018-01-30 10:59:05 -08:00
qres = iavlStore.Query(query0)
require.Equal(t, uint32(0), qres.Code)
require.Equal(t, v1, qres.Value)
2018-01-30 10:59:05 -08:00
}
func BenchmarkIAVLIteratorNext(b *testing.B) {
db := dbm.NewMemDB()
treeSize := 1000
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(b, err)
for i := 0; i < treeSize; i++ {
key := randBytes(4)
value := randBytes(50)
tree.Set(key, value)
}
2020-06-22 13:31:33 -07:00
iavlStore := UnsafeNewStore(tree)
2019-02-01 17:03:09 -08:00
iterators := make([]types.Iterator, b.N/treeSize)
for i := 0; i < len(iterators); i++ {
iterators[i] = iavlStore.Iterator([]byte{0}, []byte{255, 255, 255, 255, 255})
}
b.ResetTimer()
for i := 0; i < len(iterators); i++ {
iter := iterators[i]
for j := 0; j < treeSize; j++ {
iter.Next()
}
}
}
func TestSetInitialVersion(t *testing.T) {
testCases := []struct {
name string
storeFn func(db *dbm.MemDB) *Store
expPanic bool
}{
{
"works with a mutable tree",
func(db *dbm.MemDB) *Store {
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
store := UnsafeNewStore(tree)
return store
}, false,
},
{
"throws error on immutable tree",
func(db *dbm.MemDB) *Store {
tree, err := iavl.NewMutableTree(db, cacheSize)
require.NoError(t, err)
store := UnsafeNewStore(tree)
_, version, err := store.tree.SaveVersion()
require.NoError(t, err)
require.Equal(t, int64(1), version)
store, err = store.GetImmutable(1)
require.NoError(t, err)
return store
}, true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
db := dbm.NewMemDB()
store := tc.storeFn(db)
if tc.expPanic {
require.Panics(t, func() { store.SetInitialVersion(5) })
} else {
store.SetInitialVersion(5)
cid := store.Commit()
require.Equal(t, int64(5), cid.GetVersion())
}
})
}
}