github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/jsonrpc_test.go (about)

     1  package jsonrpc
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	crand "crypto/rand"
     7  	"encoding/json"
     8  	"errors"
     9  	mrand "math/rand"
    10  	"net/http"
    11  	"os/exec"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/fortytw2/leaktest"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	tmbytes "github.com/ari-anchor/sei-tendermint/libs/bytes"
    20  	"github.com/ari-anchor/sei-tendermint/libs/log"
    21  	"github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/client"
    22  	"github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/server"
    23  )
    24  
    25  // Client and Server should work over tcp or unix sockets
    26  const (
    27  	tcpAddr = "tcp://127.0.0.1:47768"
    28  
    29  	unixSocket = "/tmp/rpc_test.sock"
    30  	unixAddr   = "unix://" + unixSocket
    31  
    32  	websocketEndpoint = "/websocket/endpoint"
    33  
    34  	testVal = "acbd"
    35  )
    36  
    37  type RequestEcho struct {
    38  	Value string `json:"arg"`
    39  }
    40  
    41  type ResultEcho struct {
    42  	Value string `json:"value"`
    43  }
    44  
    45  type RequestEchoInt struct {
    46  	Value int `json:"arg"`
    47  }
    48  
    49  type ResultEchoInt struct {
    50  	Value int `json:"value"`
    51  }
    52  
    53  type RequestEchoBytes struct {
    54  	Value []byte `json:"arg"`
    55  }
    56  
    57  type ResultEchoBytes struct {
    58  	Value []byte `json:"value"`
    59  }
    60  
    61  type RequestEchoDataBytes struct {
    62  	Value tmbytes.HexBytes `json:"arg"`
    63  }
    64  
    65  type ResultEchoDataBytes struct {
    66  	Value tmbytes.HexBytes `json:"value"`
    67  }
    68  
    69  // Define some routes
    70  var Routes = map[string]*server.RPCFunc{
    71  	"echo":            server.NewRPCFunc(EchoResult),
    72  	"echo_ws":         server.NewWSRPCFunc(EchoWSResult),
    73  	"echo_bytes":      server.NewRPCFunc(EchoBytesResult),
    74  	"echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult),
    75  	"echo_int":        server.NewRPCFunc(EchoIntResult),
    76  }
    77  
    78  func EchoResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) {
    79  	return &ResultEcho{v.Value}, nil
    80  }
    81  
    82  func EchoWSResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) {
    83  	return &ResultEcho{v.Value}, nil
    84  }
    85  
    86  func EchoIntResult(ctx context.Context, v *RequestEchoInt) (*ResultEchoInt, error) {
    87  	return &ResultEchoInt{v.Value}, nil
    88  }
    89  
    90  func EchoBytesResult(ctx context.Context, v *RequestEchoBytes) (*ResultEchoBytes, error) {
    91  	return &ResultEchoBytes{v.Value}, nil
    92  }
    93  
    94  func EchoDataBytesResult(ctx context.Context, v *RequestEchoDataBytes) (*ResultEchoDataBytes, error) {
    95  	return &ResultEchoDataBytes{v.Value}, nil
    96  }
    97  
    98  // launch unix and tcp servers
    99  func setup(ctx context.Context, t *testing.T, logger log.Logger) error {
   100  	cmd := exec.Command("rm", "-f", unixSocket)
   101  	err := cmd.Start()
   102  	if err != nil {
   103  		return err
   104  	}
   105  	if err = cmd.Wait(); err != nil {
   106  		return err
   107  	}
   108  
   109  	tcpLogger := logger.With("socket", "tcp")
   110  	mux := http.NewServeMux()
   111  	server.RegisterRPCFuncs(mux, Routes, tcpLogger)
   112  	wm := server.NewWebsocketManager(tcpLogger, Routes, server.ReadWait(5*time.Second), server.PingPeriod(1*time.Second))
   113  	mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
   114  	config := server.DefaultConfig()
   115  	listener1, err := server.Listen(tcpAddr, config.MaxOpenConnections)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	go func() {
   120  		if err := server.Serve(ctx, listener1, mux, tcpLogger, config); err != nil {
   121  			if !errors.Is(err, http.ErrServerClosed) {
   122  				require.NoError(t, err)
   123  			}
   124  		}
   125  	}()
   126  
   127  	unixLogger := logger.With("socket", "unix")
   128  	mux2 := http.NewServeMux()
   129  	server.RegisterRPCFuncs(mux2, Routes, unixLogger)
   130  	wm = server.NewWebsocketManager(unixLogger, Routes)
   131  	mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
   132  	listener2, err := server.Listen(unixAddr, config.MaxOpenConnections)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	go func() {
   137  		if err := server.Serve(ctx, listener2, mux2, unixLogger, config); err != nil {
   138  			if !errors.Is(err, http.ErrServerClosed) {
   139  				require.NoError(t, err)
   140  			}
   141  		}
   142  	}()
   143  
   144  	// wait for servers to start
   145  	time.Sleep(time.Second * 2)
   146  	return nil
   147  }
   148  
   149  func echoViaHTTP(ctx context.Context, cl client.Caller, val string) (string, error) {
   150  	params := map[string]interface{}{
   151  		"arg": val,
   152  	}
   153  	result := new(ResultEcho)
   154  	if err := cl.Call(ctx, "echo", params, result); err != nil {
   155  		return "", err
   156  	}
   157  	return result.Value, nil
   158  }
   159  
   160  func echoIntViaHTTP(ctx context.Context, cl client.Caller, val int) (int, error) {
   161  	params := map[string]interface{}{
   162  		"arg": val,
   163  	}
   164  	result := new(ResultEchoInt)
   165  	if err := cl.Call(ctx, "echo_int", params, result); err != nil {
   166  		return 0, err
   167  	}
   168  	return result.Value, nil
   169  }
   170  
   171  func echoBytesViaHTTP(ctx context.Context, cl client.Caller, bytes []byte) ([]byte, error) {
   172  	params := map[string]interface{}{
   173  		"arg": bytes,
   174  	}
   175  	result := new(ResultEchoBytes)
   176  	if err := cl.Call(ctx, "echo_bytes", params, result); err != nil {
   177  		return []byte{}, err
   178  	}
   179  	return result.Value, nil
   180  }
   181  
   182  func echoDataBytesViaHTTP(ctx context.Context, cl client.Caller, bytes tmbytes.HexBytes) (tmbytes.HexBytes, error) {
   183  	params := map[string]interface{}{
   184  		"arg": bytes,
   185  	}
   186  	result := new(ResultEchoDataBytes)
   187  	if err := cl.Call(ctx, "echo_data_bytes", params, result); err != nil {
   188  		return []byte{}, err
   189  	}
   190  	return result.Value, nil
   191  }
   192  
   193  func testWithHTTPClient(ctx context.Context, t *testing.T, cl client.Caller) {
   194  	val := testVal
   195  	got, err := echoViaHTTP(ctx, cl, val)
   196  	require.NoError(t, err)
   197  	assert.Equal(t, got, val)
   198  
   199  	val2 := randBytes(t)
   200  	got2, err := echoBytesViaHTTP(ctx, cl, val2)
   201  	require.NoError(t, err)
   202  	assert.Equal(t, got2, val2)
   203  
   204  	val3 := tmbytes.HexBytes(randBytes(t))
   205  	got3, err := echoDataBytesViaHTTP(ctx, cl, val3)
   206  	require.NoError(t, err)
   207  	assert.Equal(t, got3, val3)
   208  
   209  	val4 := mrand.Intn(10000)
   210  	got4, err := echoIntViaHTTP(ctx, cl, val4)
   211  	require.NoError(t, err)
   212  	assert.Equal(t, got4, val4)
   213  }
   214  
   215  func echoViaWS(ctx context.Context, cl *client.WSClient, val string) (string, error) {
   216  	params := map[string]interface{}{
   217  		"arg": val,
   218  	}
   219  	err := cl.Call(ctx, "echo", params)
   220  	if err != nil {
   221  		return "", err
   222  	}
   223  
   224  	msg := <-cl.ResponsesCh
   225  	if msg.Error != nil {
   226  		return "", err
   227  
   228  	}
   229  	result := new(ResultEcho)
   230  	err = json.Unmarshal(msg.Result, result)
   231  	if err != nil {
   232  		return "", nil
   233  	}
   234  	return result.Value, nil
   235  }
   236  
   237  func echoBytesViaWS(ctx context.Context, cl *client.WSClient, bytes []byte) ([]byte, error) {
   238  	params := map[string]interface{}{
   239  		"arg": bytes,
   240  	}
   241  	err := cl.Call(ctx, "echo_bytes", params)
   242  	if err != nil {
   243  		return []byte{}, err
   244  	}
   245  
   246  	msg := <-cl.ResponsesCh
   247  	if msg.Error != nil {
   248  		return []byte{}, msg.Error
   249  
   250  	}
   251  	result := new(ResultEchoBytes)
   252  	err = json.Unmarshal(msg.Result, result)
   253  	if err != nil {
   254  		return []byte{}, nil
   255  	}
   256  	return result.Value, nil
   257  }
   258  
   259  func testWithWSClient(ctx context.Context, t *testing.T, cl *client.WSClient) {
   260  	val := testVal
   261  	got, err := echoViaWS(ctx, cl, val)
   262  	require.NoError(t, err)
   263  	assert.Equal(t, got, val)
   264  
   265  	val2 := randBytes(t)
   266  	got2, err := echoBytesViaWS(ctx, cl, val2)
   267  	require.NoError(t, err)
   268  	assert.Equal(t, got2, val2)
   269  }
   270  
   271  //-------------
   272  
   273  func TestRPC(t *testing.T) {
   274  	ctx, cancel := context.WithCancel(context.Background())
   275  	defer cancel()
   276  	logger := log.NewNopLogger()
   277  
   278  	t.Cleanup(leaktest.Check(t))
   279  	require.NoError(t, setup(ctx, t, logger))
   280  	t.Run("ServersAndClientsBasic", func(t *testing.T) {
   281  		serverAddrs := [...]string{tcpAddr, unixAddr}
   282  		for _, addr := range serverAddrs {
   283  			t.Run(addr, func(t *testing.T) {
   284  				tctx, tcancel := context.WithCancel(ctx)
   285  				defer tcancel()
   286  
   287  				logger := log.NewNopLogger()
   288  
   289  				cl2, err := client.New(addr)
   290  				require.NoError(t, err)
   291  				t.Logf("testing server with JSONRPC client")
   292  				testWithHTTPClient(tctx, t, cl2)
   293  
   294  				cl3, err := client.NewWS(addr, websocketEndpoint)
   295  				require.NoError(t, err)
   296  				cl3.Logger = logger
   297  				err = cl3.Start(tctx)
   298  				require.NoError(t, err)
   299  				t.Logf("testing server with WS client")
   300  				testWithWSClient(tctx, t, cl3)
   301  			})
   302  		}
   303  	})
   304  	t.Run("WSNewWSRPCFunc", func(t *testing.T) {
   305  		t.Cleanup(leaktest.CheckTimeout(t, 4*time.Second))
   306  
   307  		cl, err := client.NewWS(tcpAddr, websocketEndpoint)
   308  		require.NoError(t, err)
   309  		cl.Logger = log.NewNopLogger()
   310  		tctx, tcancel := context.WithCancel(ctx)
   311  		defer tcancel()
   312  
   313  		require.NoError(t, cl.Start(tctx))
   314  		t.Cleanup(func() {
   315  			if err := cl.Stop(); err != nil {
   316  				t.Error(err)
   317  			}
   318  		})
   319  
   320  		val := testVal
   321  		params := map[string]interface{}{
   322  			"arg": val,
   323  		}
   324  		err = cl.Call(tctx, "echo_ws", params)
   325  		require.NoError(t, err)
   326  
   327  		select {
   328  		case <-tctx.Done():
   329  			t.Fatal(tctx.Err())
   330  		case msg := <-cl.ResponsesCh:
   331  			if msg.Error != nil {
   332  				t.Fatal(err)
   333  			}
   334  			result := new(ResultEcho)
   335  			err = json.Unmarshal(msg.Result, result)
   336  			require.NoError(t, err)
   337  			got := result.Value
   338  			assert.Equal(t, got, val)
   339  
   340  		}
   341  	})
   342  	t.Run("WSClientPingPong", func(t *testing.T) {
   343  		// TestWSClientPingPong checks that a client & server exchange pings
   344  		// & pongs so connection stays alive.
   345  		t.Cleanup(leaktest.CheckTimeout(t, 4*time.Second))
   346  
   347  		cl, err := client.NewWS(tcpAddr, websocketEndpoint)
   348  		require.NoError(t, err)
   349  		cl.Logger = log.NewNopLogger()
   350  
   351  		tctx, tcancel := context.WithCancel(ctx)
   352  		defer tcancel()
   353  
   354  		require.NoError(t, cl.Start(tctx))
   355  		t.Cleanup(func() {
   356  			if err := cl.Stop(); err != nil {
   357  				t.Error(err)
   358  			}
   359  		})
   360  
   361  		time.Sleep(6 * time.Second)
   362  	})
   363  }
   364  
   365  func randBytes(t *testing.T) []byte {
   366  	n := mrand.Intn(10) + 2
   367  	buf := make([]byte, n)
   368  	_, err := crand.Read(buf)
   369  	require.NoError(t, err)
   370  	return bytes.ReplaceAll(buf, []byte("="), []byte{100})
   371  }