github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/abci/example/kvstore/kvstore_test.go (about)

     1  package kvstore
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sort"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/badrootd/nibiru-cometbft/libs/log"
    12  	"github.com/badrootd/nibiru-cometbft/libs/service"
    13  
    14  	abcicli "github.com/badrootd/nibiru-cometbft/abci/client"
    15  	"github.com/badrootd/nibiru-cometbft/abci/example/code"
    16  	abciserver "github.com/badrootd/nibiru-cometbft/abci/server"
    17  	"github.com/badrootd/nibiru-cometbft/abci/types"
    18  	cmtproto "github.com/badrootd/nibiru-cometbft/proto/tendermint/types"
    19  )
    20  
    21  const (
    22  	testKey   = "abc"
    23  	testValue = "def"
    24  )
    25  
    26  func testKVStore(t *testing.T, app types.Application, tx []byte, key, value string) {
    27  	req := types.RequestDeliverTx{Tx: tx}
    28  	ar := app.DeliverTx(req)
    29  	require.False(t, ar.IsErr(), ar)
    30  	// repeating tx doesn't raise error
    31  	ar = app.DeliverTx(req)
    32  	require.False(t, ar.IsErr(), ar)
    33  	// commit
    34  	app.Commit()
    35  
    36  	info := app.Info(types.RequestInfo{})
    37  	require.NotZero(t, info.LastBlockHeight)
    38  
    39  	// make sure query is fine
    40  	resQuery := app.Query(types.RequestQuery{
    41  		Path: "/store",
    42  		Data: []byte(key),
    43  	})
    44  	require.Equal(t, code.CodeTypeOK, resQuery.Code)
    45  	require.Equal(t, key, string(resQuery.Key))
    46  	require.Equal(t, value, string(resQuery.Value))
    47  	require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
    48  
    49  	// make sure proof is fine
    50  	resQuery = app.Query(types.RequestQuery{
    51  		Path:  "/store",
    52  		Data:  []byte(key),
    53  		Prove: true,
    54  	})
    55  	require.EqualValues(t, code.CodeTypeOK, resQuery.Code)
    56  	require.Equal(t, key, string(resQuery.Key))
    57  	require.Equal(t, value, string(resQuery.Value))
    58  	require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
    59  }
    60  
    61  func TestKVStoreKV(t *testing.T) {
    62  	kvstore := NewApplication()
    63  	key := testKey
    64  	value := key
    65  	tx := []byte(key)
    66  	testKVStore(t, kvstore, tx, key, value)
    67  
    68  	value = testValue
    69  	tx = []byte(key + "=" + value)
    70  	testKVStore(t, kvstore, tx, key, value)
    71  }
    72  
    73  func TestPersistentKVStoreEmptyTX(t *testing.T) {
    74  	dir, err := os.MkdirTemp("/tmp", "abci-kvstore-test") // TODO
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  	kvstore := NewPersistentKVStoreApplication(dir)
    79  	tx := []byte("")
    80  	reqCheck := types.RequestCheckTx{Tx: tx}
    81  	resCheck := kvstore.CheckTx(reqCheck)
    82  	require.Equal(t, resCheck.Code, code.CodeTypeRejected)
    83  
    84  	txs := make([][]byte, 0, 4)
    85  	txs = append(txs, []byte("key=value"), []byte("key"), []byte(""), []byte("kee=value"))
    86  	reqPrepare := types.RequestPrepareProposal{Txs: txs, MaxTxBytes: 10 * 1024}
    87  	resPrepare := kvstore.PrepareProposal(reqPrepare)
    88  	require.Equal(t, len(reqPrepare.Txs), len(resPrepare.Txs)+1, "Empty transaction not properly removed")
    89  }
    90  
    91  func TestPersistentKVStoreKV(t *testing.T) {
    92  	dir, err := os.MkdirTemp("/tmp", "abci-kvstore-test") // TODO
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  	kvstore := NewPersistentKVStoreApplication(dir)
    97  	key := testKey
    98  	value := key
    99  	tx := []byte(key)
   100  	testKVStore(t, kvstore, tx, key, value)
   101  
   102  	value = testValue
   103  	tx = []byte(key + "=" + value)
   104  	testKVStore(t, kvstore, tx, key, value)
   105  }
   106  
   107  func TestPersistentKVStoreInfo(t *testing.T) {
   108  	dir, err := os.MkdirTemp("/tmp", "abci-kvstore-test") // TODO
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	kvstore := NewPersistentKVStoreApplication(dir)
   113  	InitKVStore(kvstore)
   114  	height := int64(0)
   115  
   116  	resInfo := kvstore.Info(types.RequestInfo{})
   117  	if resInfo.LastBlockHeight != height {
   118  		t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight)
   119  	}
   120  
   121  	// make and apply block
   122  	height = int64(1)
   123  	hash := []byte("foo")
   124  	header := cmtproto.Header{
   125  		Height: height,
   126  	}
   127  	kvstore.BeginBlock(types.RequestBeginBlock{Hash: hash, Header: header})
   128  	kvstore.EndBlock(types.RequestEndBlock{Height: header.Height})
   129  	kvstore.Commit()
   130  
   131  	resInfo = kvstore.Info(types.RequestInfo{})
   132  	if resInfo.LastBlockHeight != height {
   133  		t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight)
   134  	}
   135  
   136  }
   137  
   138  // add a validator, remove a validator, update a validator
   139  func TestValUpdates(t *testing.T) {
   140  	dir, err := os.MkdirTemp("/tmp", "abci-kvstore-test") // TODO
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	kvstore := NewPersistentKVStoreApplication(dir)
   145  
   146  	// init with some validators
   147  	total := 10
   148  	nInit := 5
   149  	vals := RandVals(total)
   150  	// initialize with the first nInit
   151  	kvstore.InitChain(types.RequestInitChain{
   152  		Validators: vals[:nInit],
   153  	})
   154  
   155  	vals1, vals2 := vals[:nInit], kvstore.Validators()
   156  	valsEqual(t, vals1, vals2)
   157  
   158  	var v1, v2, v3 types.ValidatorUpdate
   159  
   160  	// add some validators
   161  	v1, v2 = vals[nInit], vals[nInit+1]
   162  	diff := []types.ValidatorUpdate{v1, v2}
   163  	tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power)
   164  	tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power)
   165  
   166  	makeApplyBlock(t, kvstore, 1, diff, tx1, tx2)
   167  
   168  	vals1, vals2 = vals[:nInit+2], kvstore.Validators()
   169  	valsEqual(t, vals1, vals2)
   170  
   171  	// remove some validators
   172  	v1, v2, v3 = vals[nInit-2], vals[nInit-1], vals[nInit]
   173  	v1.Power = 0
   174  	v2.Power = 0
   175  	v3.Power = 0
   176  	diff = []types.ValidatorUpdate{v1, v2, v3}
   177  	tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power)
   178  	tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power)
   179  	tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power)
   180  
   181  	makeApplyBlock(t, kvstore, 2, diff, tx1, tx2, tx3)
   182  
   183  	vals1 = append(vals[:nInit-2], vals[nInit+1]) //nolint: gocritic
   184  	vals2 = kvstore.Validators()
   185  	valsEqual(t, vals1, vals2)
   186  
   187  	// update some validators
   188  	v1 = vals[0]
   189  	if v1.Power == 5 {
   190  		v1.Power = 6
   191  	} else {
   192  		v1.Power = 5
   193  	}
   194  	diff = []types.ValidatorUpdate{v1}
   195  	tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power)
   196  
   197  	makeApplyBlock(t, kvstore, 3, diff, tx1)
   198  
   199  	vals1 = append([]types.ValidatorUpdate{v1}, vals1[1:]...)
   200  	vals2 = kvstore.Validators()
   201  	valsEqual(t, vals1, vals2)
   202  
   203  }
   204  
   205  func makeApplyBlock(
   206  	t *testing.T,
   207  	kvstore types.Application,
   208  	heightInt int,
   209  	diff []types.ValidatorUpdate,
   210  	txs ...[]byte) {
   211  	// make and apply block
   212  	height := int64(heightInt)
   213  	hash := []byte("foo")
   214  	header := cmtproto.Header{
   215  		Height: height,
   216  	}
   217  
   218  	kvstore.BeginBlock(types.RequestBeginBlock{Hash: hash, Header: header})
   219  	for _, tx := range txs {
   220  		if r := kvstore.DeliverTx(types.RequestDeliverTx{Tx: tx}); r.IsErr() {
   221  			t.Fatal(r)
   222  		}
   223  	}
   224  	resEndBlock := kvstore.EndBlock(types.RequestEndBlock{Height: header.Height})
   225  	kvstore.Commit()
   226  
   227  	valsEqual(t, diff, resEndBlock.ValidatorUpdates)
   228  
   229  }
   230  
   231  // order doesn't matter
   232  func valsEqual(t *testing.T, vals1, vals2 []types.ValidatorUpdate) {
   233  	if len(vals1) != len(vals2) {
   234  		t.Fatalf("vals dont match in len. got %d, expected %d", len(vals2), len(vals1))
   235  	}
   236  	sort.Sort(types.ValidatorUpdates(vals1))
   237  	sort.Sort(types.ValidatorUpdates(vals2))
   238  	for i, v1 := range vals1 {
   239  		v2 := vals2[i]
   240  		if !v1.PubKey.Equal(v2.PubKey) ||
   241  			v1.Power != v2.Power {
   242  			t.Fatalf("vals dont match at index %d. got %X/%d , expected %X/%d", i, v2.PubKey, v2.Power, v1.PubKey, v1.Power)
   243  		}
   244  	}
   245  }
   246  
   247  func makeSocketClientServer(app types.Application, name string) (abcicli.Client, service.Service, error) {
   248  	// Start the listener
   249  	socket := fmt.Sprintf("unix://%s.sock", name)
   250  	logger := log.TestingLogger()
   251  
   252  	server := abciserver.NewSocketServer(socket, app)
   253  	server.SetLogger(logger.With("module", "abci-server"))
   254  	if err := server.Start(); err != nil {
   255  		return nil, nil, err
   256  	}
   257  
   258  	// Connect to the socket
   259  	client := abcicli.NewSocketClient(socket, false)
   260  	client.SetLogger(logger.With("module", "abci-client"))
   261  	if err := client.Start(); err != nil {
   262  		if err = server.Stop(); err != nil {
   263  			return nil, nil, err
   264  		}
   265  		return nil, nil, err
   266  	}
   267  
   268  	return client, server, nil
   269  }
   270  
   271  func makeGRPCClientServer(app types.Application, name string) (abcicli.Client, service.Service, error) {
   272  	// Start the listener
   273  	socket := fmt.Sprintf("unix://%s.sock", name)
   274  	logger := log.TestingLogger()
   275  
   276  	gapp := types.NewGRPCApplication(app)
   277  	server := abciserver.NewGRPCServer(socket, gapp)
   278  	server.SetLogger(logger.With("module", "abci-server"))
   279  	if err := server.Start(); err != nil {
   280  		return nil, nil, err
   281  	}
   282  
   283  	client := abcicli.NewGRPCClient(socket, true)
   284  	client.SetLogger(logger.With("module", "abci-client"))
   285  	if err := client.Start(); err != nil {
   286  		if err := server.Stop(); err != nil {
   287  			return nil, nil, err
   288  		}
   289  		return nil, nil, err
   290  	}
   291  	return client, server, nil
   292  }
   293  
   294  func TestClientServer(t *testing.T) {
   295  	// set up socket app
   296  	kvstore := NewApplication()
   297  	client, server, err := makeSocketClientServer(kvstore, "kvstore-socket")
   298  	require.NoError(t, err)
   299  	t.Cleanup(func() {
   300  		if err := server.Stop(); err != nil {
   301  			t.Error(err)
   302  		}
   303  	})
   304  	t.Cleanup(func() {
   305  		if err := client.Stop(); err != nil {
   306  			t.Error(err)
   307  		}
   308  	})
   309  
   310  	runClientTests(t, client)
   311  
   312  	// set up grpc app
   313  	kvstore = NewApplication()
   314  	gclient, gserver, err := makeGRPCClientServer(kvstore, "/tmp/kvstore-grpc")
   315  	require.NoError(t, err)
   316  
   317  	t.Cleanup(func() {
   318  		if err := gserver.Stop(); err != nil {
   319  			t.Error(err)
   320  		}
   321  	})
   322  	t.Cleanup(func() {
   323  		if err := gclient.Stop(); err != nil {
   324  			t.Error(err)
   325  		}
   326  	})
   327  
   328  	runClientTests(t, gclient)
   329  }
   330  
   331  func runClientTests(t *testing.T, client abcicli.Client) {
   332  	// run some tests....
   333  	key := testKey
   334  	value := key
   335  	tx := []byte(key)
   336  	testClient(t, client, tx, key, value)
   337  
   338  	value = testValue
   339  	tx = []byte(key + "=" + value)
   340  	testClient(t, client, tx, key, value)
   341  }
   342  
   343  func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) {
   344  	ar, err := app.DeliverTxSync(types.RequestDeliverTx{Tx: tx})
   345  	require.NoError(t, err)
   346  	require.False(t, ar.IsErr(), ar)
   347  	// repeating tx doesn't raise error
   348  	ar, err = app.DeliverTxSync(types.RequestDeliverTx{Tx: tx})
   349  	require.NoError(t, err)
   350  	require.False(t, ar.IsErr(), ar)
   351  	// commit
   352  	_, err = app.CommitSync()
   353  	require.NoError(t, err)
   354  
   355  	info, err := app.InfoSync(types.RequestInfo{})
   356  	require.NoError(t, err)
   357  	require.NotZero(t, info.LastBlockHeight)
   358  
   359  	// make sure query is fine
   360  	resQuery, err := app.QuerySync(types.RequestQuery{
   361  		Path: "/store",
   362  		Data: []byte(key),
   363  	})
   364  	require.Nil(t, err)
   365  	require.Equal(t, code.CodeTypeOK, resQuery.Code)
   366  	require.Equal(t, key, string(resQuery.Key))
   367  	require.Equal(t, value, string(resQuery.Value))
   368  	require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
   369  
   370  	// make sure proof is fine
   371  	resQuery, err = app.QuerySync(types.RequestQuery{
   372  		Path:  "/store",
   373  		Data:  []byte(key),
   374  		Prove: true,
   375  	})
   376  	require.Nil(t, err)
   377  	require.Equal(t, code.CodeTypeOK, resQuery.Code)
   378  	require.Equal(t, key, string(resQuery.Key))
   379  	require.Equal(t, value, string(resQuery.Value))
   380  	require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
   381  }