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 }