github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/client/batch_test.go (about) 1 package client 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/gnolang/gno/tm2/pkg/amino" 8 abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" 9 cstypes "github.com/gnolang/gno/tm2/pkg/bft/consensus/types" 10 ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 11 types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" 12 bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" 13 "github.com/gnolang/gno/tm2/pkg/p2p" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 // generateMockBatchClient generates a common 19 // mock batch handling client 20 func generateMockBatchClient( 21 t *testing.T, 22 method string, 23 expectedRequests int, 24 commonResult any, 25 ) *mockClient { 26 t.Helper() 27 28 return &mockClient{ 29 sendBatchFn: func(_ context.Context, requests types.RPCRequests) (types.RPCResponses, error) { 30 require.Len(t, requests, expectedRequests) 31 32 responses := make(types.RPCResponses, len(requests)) 33 34 for index, request := range requests { 35 require.Equal(t, "2.0", request.JSONRPC) 36 require.NotEmpty(t, request.ID) 37 require.Equal(t, method, request.Method) 38 39 result, err := amino.MarshalJSON(commonResult) 40 require.NoError(t, err) 41 42 response := types.RPCResponse{ 43 JSONRPC: "2.0", 44 ID: request.ID, 45 Result: result, 46 Error: nil, 47 } 48 49 responses[index] = response 50 } 51 52 return responses, nil 53 }, 54 } 55 } 56 57 func TestRPCBatch_Count(t *testing.T) { 58 t.Parallel() 59 60 var ( 61 c = NewRPCClient(&mockClient{}) 62 batch = c.NewBatch() 63 ) 64 65 // Make sure the batch is initially empty 66 assert.Equal(t, 0, batch.Count()) 67 68 // Add a dummy request 69 require.NoError(t, batch.Status()) 70 71 // Make sure the request is enqueued 72 assert.Equal(t, 1, batch.Count()) 73 } 74 75 func TestRPCBatch_Clear(t *testing.T) { 76 t.Parallel() 77 78 var ( 79 c = NewRPCClient(&mockClient{}) 80 batch = c.NewBatch() 81 ) 82 83 // Add a dummy request 84 require.NoError(t, batch.Status()) 85 86 // Make sure the request is enqueued 87 assert.Equal(t, 1, batch.Count()) 88 89 // Clear the batch 90 assert.Equal(t, 1, batch.Clear()) 91 92 // Make sure no request is enqueued 93 assert.Equal(t, 0, batch.Count()) 94 } 95 96 func TestRPCBatch_Send(t *testing.T) { 97 t.Parallel() 98 99 t.Run("empty batch", func(t *testing.T) { 100 t.Parallel() 101 102 var ( 103 c = NewRPCClient(&mockClient{}) 104 batch = c.NewBatch() 105 ) 106 107 res, err := batch.Send(context.Background()) 108 109 assert.ErrorIs(t, err, errEmptyBatch) 110 assert.Nil(t, res) 111 }) 112 113 t.Run("valid batch", func(t *testing.T) { 114 t.Parallel() 115 116 var ( 117 numRequests = 10 118 expectedStatus = &ctypes.ResultStatus{ 119 NodeInfo: p2p.NodeInfo{ 120 Moniker: "dummy", 121 }, 122 } 123 124 mockClient = generateMockBatchClient(t, statusMethod, 10, expectedStatus) 125 126 c = NewRPCClient(mockClient) 127 batch = c.NewBatch() 128 ) 129 130 // Enqueue the requests 131 for i := 0; i < numRequests; i++ { 132 require.NoError(t, batch.Status()) 133 } 134 135 // Send the batch 136 results, err := batch.Send(context.Background()) 137 require.NoError(t, err) 138 139 // Validate the results 140 assert.Len(t, results, numRequests) 141 142 for _, result := range results { 143 castResult, ok := result.(*ctypes.ResultStatus) 144 require.True(t, ok) 145 146 assert.Equal(t, expectedStatus, castResult) 147 } 148 }) 149 } 150 151 func TestRPCBatch_Endpoints(t *testing.T) { 152 t.Parallel() 153 154 testTable := []struct { 155 method string 156 expectedResult any 157 batchCallback func(*RPCBatch) 158 extractCallback func(any) any 159 }{ 160 { 161 statusMethod, 162 &ctypes.ResultStatus{ 163 NodeInfo: p2p.NodeInfo{ 164 Moniker: "dummy", 165 }, 166 }, 167 func(batch *RPCBatch) { 168 require.NoError(t, batch.Status()) 169 }, 170 func(result any) any { 171 castResult, ok := result.(*ctypes.ResultStatus) 172 require.True(t, ok) 173 174 return castResult 175 }, 176 }, 177 { 178 abciInfoMethod, 179 &ctypes.ResultABCIInfo{ 180 Response: abci.ResponseInfo{ 181 LastBlockAppHash: []byte("dummy"), 182 }, 183 }, 184 func(batch *RPCBatch) { 185 require.NoError(t, batch.ABCIInfo()) 186 }, 187 func(result any) any { 188 castResult, ok := result.(*ctypes.ResultABCIInfo) 189 require.True(t, ok) 190 191 return castResult 192 }, 193 }, 194 { 195 abciQueryMethod, 196 &ctypes.ResultABCIQuery{ 197 Response: abci.ResponseQuery{ 198 Value: []byte("dummy"), 199 }, 200 }, 201 func(batch *RPCBatch) { 202 require.NoError(t, batch.ABCIQuery("path", []byte("dummy"))) 203 }, 204 func(result any) any { 205 castResult, ok := result.(*ctypes.ResultABCIQuery) 206 require.True(t, ok) 207 208 return castResult 209 }, 210 }, 211 { 212 broadcastTxCommitMethod, 213 &ctypes.ResultBroadcastTxCommit{ 214 Hash: []byte("dummy"), 215 }, 216 func(batch *RPCBatch) { 217 require.NoError(t, batch.BroadcastTxCommit([]byte("dummy"))) 218 }, 219 func(result any) any { 220 castResult, ok := result.(*ctypes.ResultBroadcastTxCommit) 221 require.True(t, ok) 222 223 return castResult 224 }, 225 }, 226 { 227 broadcastTxAsyncMethod, 228 &ctypes.ResultBroadcastTx{ 229 Hash: []byte("dummy"), 230 }, 231 func(batch *RPCBatch) { 232 require.NoError(t, batch.BroadcastTxAsync([]byte("dummy"))) 233 }, 234 func(result any) any { 235 castResult, ok := result.(*ctypes.ResultBroadcastTx) 236 require.True(t, ok) 237 238 return castResult 239 }, 240 }, 241 { 242 broadcastTxSyncMethod, 243 &ctypes.ResultBroadcastTx{ 244 Hash: []byte("dummy"), 245 }, 246 func(batch *RPCBatch) { 247 require.NoError(t, batch.BroadcastTxSync([]byte("dummy"))) 248 }, 249 func(result any) any { 250 castResult, ok := result.(*ctypes.ResultBroadcastTx) 251 require.True(t, ok) 252 253 return castResult 254 }, 255 }, 256 { 257 unconfirmedTxsMethod, 258 &ctypes.ResultUnconfirmedTxs{ 259 Count: 10, 260 }, 261 func(batch *RPCBatch) { 262 require.NoError(t, batch.UnconfirmedTxs(0)) 263 }, 264 func(result any) any { 265 castResult, ok := result.(*ctypes.ResultUnconfirmedTxs) 266 require.True(t, ok) 267 268 return castResult 269 }, 270 }, 271 { 272 numUnconfirmedTxsMethod, 273 &ctypes.ResultUnconfirmedTxs{ 274 Count: 10, 275 }, 276 func(batch *RPCBatch) { 277 require.NoError(t, batch.NumUnconfirmedTxs()) 278 }, 279 func(result any) any { 280 castResult, ok := result.(*ctypes.ResultUnconfirmedTxs) 281 require.True(t, ok) 282 283 return castResult 284 }, 285 }, 286 { 287 netInfoMethod, 288 &ctypes.ResultNetInfo{ 289 NPeers: 10, 290 }, 291 func(batch *RPCBatch) { 292 require.NoError(t, batch.NetInfo()) 293 }, 294 func(result any) any { 295 castResult, ok := result.(*ctypes.ResultNetInfo) 296 require.True(t, ok) 297 298 return castResult 299 }, 300 }, 301 { 302 dumpConsensusStateMethod, 303 &ctypes.ResultDumpConsensusState{ 304 RoundState: &cstypes.RoundState{ 305 Round: 10, 306 }, 307 }, 308 func(batch *RPCBatch) { 309 require.NoError(t, batch.DumpConsensusState()) 310 }, 311 func(result any) any { 312 castResult, ok := result.(*ctypes.ResultDumpConsensusState) 313 require.True(t, ok) 314 315 return castResult 316 }, 317 }, 318 { 319 consensusStateMethod, 320 &ctypes.ResultConsensusState{ 321 RoundState: cstypes.RoundStateSimple{ 322 ProposalBlockHash: []byte("dummy"), 323 }, 324 }, 325 func(batch *RPCBatch) { 326 require.NoError(t, batch.ConsensusState()) 327 }, 328 func(result any) any { 329 castResult, ok := result.(*ctypes.ResultConsensusState) 330 require.True(t, ok) 331 332 return castResult 333 }, 334 }, 335 { 336 consensusParamsMethod, 337 &ctypes.ResultConsensusParams{ 338 BlockHeight: 10, 339 }, 340 func(batch *RPCBatch) { 341 require.NoError(t, batch.ConsensusParams(nil)) 342 }, 343 func(result any) any { 344 castResult, ok := result.(*ctypes.ResultConsensusParams) 345 require.True(t, ok) 346 347 return castResult 348 }, 349 }, 350 { 351 healthMethod, 352 &ctypes.ResultHealth{}, 353 func(batch *RPCBatch) { 354 require.NoError(t, batch.Health()) 355 }, 356 func(result any) any { 357 castResult, ok := result.(*ctypes.ResultHealth) 358 require.True(t, ok) 359 360 return castResult 361 }, 362 }, 363 { 364 blockchainMethod, 365 &ctypes.ResultBlockchainInfo{ 366 LastHeight: 100, 367 }, 368 func(batch *RPCBatch) { 369 require.NoError(t, batch.BlockchainInfo(0, 0)) 370 }, 371 func(result any) any { 372 castResult, ok := result.(*ctypes.ResultBlockchainInfo) 373 require.True(t, ok) 374 375 return castResult 376 }, 377 }, 378 { 379 genesisMethod, 380 &ctypes.ResultGenesis{ 381 Genesis: &bfttypes.GenesisDoc{ 382 ChainID: "dummy", 383 }, 384 }, 385 func(batch *RPCBatch) { 386 require.NoError(t, batch.Genesis()) 387 }, 388 func(result any) any { 389 castResult, ok := result.(*ctypes.ResultGenesis) 390 require.True(t, ok) 391 392 return castResult 393 }, 394 }, 395 { 396 blockMethod, 397 &ctypes.ResultBlock{ 398 BlockMeta: &bfttypes.BlockMeta{ 399 Header: bfttypes.Header{ 400 Height: 10, 401 }, 402 }, 403 }, 404 func(batch *RPCBatch) { 405 require.NoError(t, batch.Block(nil)) 406 }, 407 func(result any) any { 408 castResult, ok := result.(*ctypes.ResultBlock) 409 require.True(t, ok) 410 411 return castResult 412 }, 413 }, 414 { 415 blockResultsMethod, 416 &ctypes.ResultBlockResults{ 417 Height: 10, 418 }, 419 func(batch *RPCBatch) { 420 require.NoError(t, batch.BlockResults(nil)) 421 }, 422 func(result any) any { 423 castResult, ok := result.(*ctypes.ResultBlockResults) 424 require.True(t, ok) 425 426 return castResult 427 }, 428 }, 429 { 430 commitMethod, 431 &ctypes.ResultCommit{ 432 CanonicalCommit: true, 433 }, 434 func(batch *RPCBatch) { 435 require.NoError(t, batch.Commit(nil)) 436 }, 437 func(result any) any { 438 castResult, ok := result.(*ctypes.ResultCommit) 439 require.True(t, ok) 440 441 return castResult 442 }, 443 }, 444 { 445 txMethod, 446 &ctypes.ResultTx{ 447 Hash: []byte("tx hash"), 448 Height: 10, 449 }, 450 func(batch *RPCBatch) { 451 require.NoError(t, batch.Tx([]byte("tx hash"))) 452 }, 453 func(result any) any { 454 castResult, ok := result.(*ctypes.ResultTx) 455 require.True(t, ok) 456 457 return castResult 458 }, 459 }, 460 { 461 validatorsMethod, 462 &ctypes.ResultValidators{ 463 BlockHeight: 10, 464 }, 465 func(batch *RPCBatch) { 466 require.NoError(t, batch.Validators(nil)) 467 }, 468 func(result any) any { 469 castResult, ok := result.(*ctypes.ResultValidators) 470 require.True(t, ok) 471 472 return castResult 473 }, 474 }, 475 } 476 477 for _, testCase := range testTable { 478 testCase := testCase 479 480 t.Run(testCase.method, func(t *testing.T) { 481 t.Parallel() 482 483 var ( 484 numRequests = 10 485 mockClient = generateMockBatchClient( 486 t, 487 testCase.method, 488 numRequests, 489 testCase.expectedResult, 490 ) 491 492 c = NewRPCClient(mockClient) 493 batch = c.NewBatch() 494 ) 495 496 // Enqueue the requests 497 for i := 0; i < numRequests; i++ { 498 testCase.batchCallback(batch) 499 } 500 501 // Send the batch 502 results, err := batch.Send(context.Background()) 503 require.NoError(t, err) 504 505 // Validate the results 506 assert.Len(t, results, numRequests) 507 508 for _, result := range results { 509 castResult := testCase.extractCallback(result) 510 511 assert.Equal(t, testCase.expectedResult, castResult) 512 } 513 }) 514 } 515 }