- package kvstore
-
- import (
- "context"
- "fmt"
- "sort"
- "testing"
-
- "github.com/fortytw2/leaktest"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/libs/log"
- "github.com/tendermint/tendermint/libs/service"
-
- abciclient "github.com/tendermint/tendermint/abci/client"
- "github.com/tendermint/tendermint/abci/example/code"
- abciserver "github.com/tendermint/tendermint/abci/server"
- "github.com/tendermint/tendermint/abci/types"
- tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
- )
-
- const (
- testKey = "abc"
- testValue = "def"
- )
-
- func testKVStore(t *testing.T, app types.Application, tx []byte, key, value string) {
- req := types.RequestFinalizeBlock{Txs: [][]byte{tx}}
- ar := app.FinalizeBlock(req)
- require.Equal(t, 1, len(ar.TxResults))
- require.False(t, ar.TxResults[0].IsErr())
- // repeating tx doesn't raise error
- ar = app.FinalizeBlock(req)
- require.Equal(t, 1, len(ar.TxResults))
- require.False(t, ar.TxResults[0].IsErr())
- // commit
- app.Commit()
-
- info := app.Info(types.RequestInfo{})
- require.NotZero(t, info.LastBlockHeight)
-
- // make sure query is fine
- resQuery := app.Query(types.RequestQuery{
- Path: "/store",
- Data: []byte(key),
- })
- require.Equal(t, code.CodeTypeOK, resQuery.Code)
- require.Equal(t, key, string(resQuery.Key))
- require.Equal(t, value, string(resQuery.Value))
- require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
-
- // make sure proof is fine
- resQuery = app.Query(types.RequestQuery{
- Path: "/store",
- Data: []byte(key),
- Prove: true,
- })
- require.EqualValues(t, code.CodeTypeOK, resQuery.Code)
- require.Equal(t, key, string(resQuery.Key))
- require.Equal(t, value, string(resQuery.Value))
- require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
- }
-
- func TestKVStoreKV(t *testing.T) {
- kvstore := NewApplication()
- key := testKey
- value := key
- tx := []byte(key)
- testKVStore(t, kvstore, tx, key, value)
-
- value = testValue
- tx = []byte(key + "=" + value)
- testKVStore(t, kvstore, tx, key, value)
- }
-
- func TestPersistentKVStoreKV(t *testing.T) {
- dir := t.TempDir()
- logger := log.NewNopLogger()
-
- kvstore := NewPersistentKVStoreApplication(logger, dir)
- key := testKey
- value := key
- tx := []byte(key)
- testKVStore(t, kvstore, tx, key, value)
-
- value = testValue
- tx = []byte(key + "=" + value)
- testKVStore(t, kvstore, tx, key, value)
- }
-
- func TestPersistentKVStoreInfo(t *testing.T) {
- dir := t.TempDir()
- logger := log.NewNopLogger()
-
- kvstore := NewPersistentKVStoreApplication(logger, dir)
- InitKVStore(kvstore)
- height := int64(0)
-
- resInfo := kvstore.Info(types.RequestInfo{})
- if resInfo.LastBlockHeight != height {
- t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight)
- }
-
- // make and apply block
- height = int64(1)
- hash := []byte("foo")
- header := tmproto.Header{
- Height: height,
- }
- kvstore.FinalizeBlock(types.RequestFinalizeBlock{Hash: hash, Header: header})
- kvstore.Commit()
-
- resInfo = kvstore.Info(types.RequestInfo{})
- if resInfo.LastBlockHeight != height {
- t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight)
- }
-
- }
-
- // add a validator, remove a validator, update a validator
- func TestValUpdates(t *testing.T) {
- kvstore := NewApplication()
-
- // init with some validators
- total := 10
- nInit := 5
- vals := RandVals(total)
- // initialize with the first nInit
- kvstore.InitChain(types.RequestInitChain{
- Validators: vals[:nInit],
- })
-
- vals1, vals2 := vals[:nInit], kvstore.Validators()
- valsEqual(t, vals1, vals2)
-
- var v1, v2, v3 types.ValidatorUpdate
-
- // add some validators
- v1, v2 = vals[nInit], vals[nInit+1]
- diff := []types.ValidatorUpdate{v1, v2}
- tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power)
- tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power)
-
- makeApplyBlock(t, kvstore, 1, diff, tx1, tx2)
-
- vals1, vals2 = vals[:nInit+2], kvstore.Validators()
- valsEqual(t, vals1, vals2)
-
- // remove some validators
- v1, v2, v3 = vals[nInit-2], vals[nInit-1], vals[nInit]
- v1.Power = 0
- v2.Power = 0
- v3.Power = 0
- diff = []types.ValidatorUpdate{v1, v2, v3}
- tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power)
- tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power)
- tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power)
-
- makeApplyBlock(t, kvstore, 2, diff, tx1, tx2, tx3)
-
- vals1 = append(vals[:nInit-2], vals[nInit+1]) // nolint: gocritic
- vals2 = kvstore.Validators()
- valsEqual(t, vals1, vals2)
-
- // update some validators
- v1 = vals[0]
- if v1.Power == 5 {
- v1.Power = 6
- } else {
- v1.Power = 5
- }
- diff = []types.ValidatorUpdate{v1}
- tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power)
-
- makeApplyBlock(t, kvstore, 3, diff, tx1)
-
- vals1 = append([]types.ValidatorUpdate{v1}, vals1[1:]...)
- vals2 = kvstore.Validators()
- valsEqual(t, vals1, vals2)
-
- }
-
- func makeApplyBlock(
- t *testing.T,
- kvstore types.Application,
- heightInt int,
- diff []types.ValidatorUpdate,
- txs ...[]byte) {
- // make and apply block
- height := int64(heightInt)
- hash := []byte("foo")
- header := tmproto.Header{
- Height: height,
- }
-
- resFinalizeBlock := kvstore.FinalizeBlock(types.RequestFinalizeBlock{
- Hash: hash,
- Header: header,
- Txs: txs,
- })
-
- kvstore.Commit()
-
- valsEqual(t, diff, resFinalizeBlock.ValidatorUpdates)
-
- }
-
- // order doesn't matter
- func valsEqual(t *testing.T, vals1, vals2 []types.ValidatorUpdate) {
- t.Helper()
- if len(vals1) != len(vals2) {
- t.Fatalf("vals dont match in len. got %d, expected %d", len(vals2), len(vals1))
- }
- sort.Sort(types.ValidatorUpdates(vals1))
- sort.Sort(types.ValidatorUpdates(vals2))
- for i, v1 := range vals1 {
- v2 := vals2[i]
- if !v1.PubKey.Equal(v2.PubKey) ||
- v1.Power != v2.Power {
- t.Fatalf("vals dont match at index %d. got %X/%d , expected %X/%d", i, v2.PubKey, v2.Power, v1.PubKey, v1.Power)
- }
- }
- }
-
- func makeSocketClientServer(
- ctx context.Context,
- t *testing.T,
- logger log.Logger,
- app types.Application,
- name string,
- ) (abciclient.Client, service.Service, error) {
- t.Helper()
-
- ctx, cancel := context.WithCancel(ctx)
- t.Cleanup(cancel)
- t.Cleanup(leaktest.Check(t))
-
- // Start the listener
- socket := fmt.Sprintf("unix://%s.sock", name)
-
- server := abciserver.NewSocketServer(logger.With("module", "abci-server"), socket, app)
- if err := server.Start(ctx); err != nil {
- cancel()
- return nil, nil, err
- }
-
- // Connect to the socket
- client := abciclient.NewSocketClient(logger.With("module", "abci-client"), socket, false)
- if err := client.Start(ctx); err != nil {
- cancel()
- return nil, nil, err
- }
-
- return client, server, nil
- }
-
- func makeGRPCClientServer(
- ctx context.Context,
- t *testing.T,
- logger log.Logger,
- app types.Application,
- name string,
- ) (abciclient.Client, service.Service, error) {
- ctx, cancel := context.WithCancel(ctx)
- t.Cleanup(cancel)
- t.Cleanup(leaktest.Check(t))
-
- // Start the listener
- socket := fmt.Sprintf("unix://%s.sock", name)
-
- gapp := types.NewGRPCApplication(app)
- server := abciserver.NewGRPCServer(logger.With("module", "abci-server"), socket, gapp)
-
- if err := server.Start(ctx); err != nil {
- cancel()
- return nil, nil, err
- }
-
- client := abciclient.NewGRPCClient(logger.With("module", "abci-client"), socket, true)
-
- if err := client.Start(ctx); err != nil {
- cancel()
- return nil, nil, err
- }
- return client, server, nil
- }
-
- func TestClientServer(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- logger := log.NewNopLogger()
-
- // set up socket app
- kvstore := NewApplication()
- client, server, err := makeSocketClientServer(ctx, t, logger, kvstore, "kvstore-socket")
- require.NoError(t, err)
- t.Cleanup(func() { cancel(); server.Wait() })
- t.Cleanup(func() { cancel(); client.Wait() })
-
- runClientTests(ctx, t, client)
-
- // set up grpc app
- kvstore = NewApplication()
- gclient, gserver, err := makeGRPCClientServer(ctx, t, logger, kvstore, "/tmp/kvstore-grpc")
- require.NoError(t, err)
-
- t.Cleanup(func() { cancel(); gserver.Wait() })
- t.Cleanup(func() { cancel(); gclient.Wait() })
-
- runClientTests(ctx, t, gclient)
- }
-
- func runClientTests(ctx context.Context, t *testing.T, client abciclient.Client) {
- // run some tests....
- key := testKey
- value := key
- tx := []byte(key)
- testClient(ctx, t, client, tx, key, value)
-
- value = testValue
- tx = []byte(key + "=" + value)
- testClient(ctx, t, client, tx, key, value)
- }
-
- func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []byte, key, value string) {
- ar, err := app.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: [][]byte{tx}})
- require.NoError(t, err)
- require.Equal(t, 1, len(ar.TxResults))
- require.False(t, ar.TxResults[0].IsErr())
- // repeating FinalizeBlock doesn't raise error
- ar, err = app.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: [][]byte{tx}})
- require.NoError(t, err)
- require.Equal(t, 1, len(ar.TxResults))
- require.False(t, ar.TxResults[0].IsErr())
- // commit
- _, err = app.Commit(ctx)
- require.NoError(t, err)
-
- info, err := app.Info(ctx, types.RequestInfo{})
- require.NoError(t, err)
- require.NotZero(t, info.LastBlockHeight)
-
- // make sure query is fine
- resQuery, err := app.Query(ctx, types.RequestQuery{
- Path: "/store",
- Data: []byte(key),
- })
- require.NoError(t, err)
- require.Equal(t, code.CodeTypeOK, resQuery.Code)
- require.Equal(t, key, string(resQuery.Key))
- require.Equal(t, value, string(resQuery.Value))
- require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
-
- // make sure proof is fine
- resQuery, err = app.Query(ctx, types.RequestQuery{
- Path: "/store",
- Data: []byte(key),
- Prove: true,
- })
- require.NoError(t, err)
- require.Equal(t, code.CodeTypeOK, resQuery.Code)
- require.Equal(t, key, string(resQuery.Key))
- require.Equal(t, value, string(resQuery.Value))
- require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
- }
|