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  }