github.com/arcology-network/consensus-engine@v1.9.0/rpc/jsonrpc/jsonrpc_test.go (about)

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