github.com/klaytn/klaytn@v1.12.1/networks/rpc/subscription_test.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2016 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from rpc/subscription_test.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package rpc
    22  
    23  import (
    24  	"context"
    25  	"encoding/json"
    26  	"fmt"
    27  	"net"
    28  	"strings"
    29  	"sync"
    30  	"testing"
    31  	"time"
    32  )
    33  
    34  type NotificationTestService struct {
    35  	mu                      sync.Mutex
    36  	unsubscribed            chan string
    37  	gotHangSubscriptionReq  chan struct{}
    38  	unblockHangSubscription chan struct{}
    39  }
    40  
    41  func (s *NotificationTestService) Echo(i int) int {
    42  	return i
    43  }
    44  
    45  func (s *NotificationTestService) Unsubscribe(subid string) {
    46  	if s.unsubscribed != nil {
    47  		s.unsubscribed <- subid
    48  	}
    49  }
    50  
    51  func (s *NotificationTestService) SomeSubscription(ctx context.Context, n, val int) (*Subscription, error) {
    52  	notifier, supported := NotifierFromContext(ctx)
    53  	if !supported {
    54  		return nil, ErrNotificationsUnsupported
    55  	}
    56  
    57  	// by explicitly creating an subscription we make sure that the subscription id is send back to the client
    58  	// before the first subscription.Notify is called. Otherwise the events might be send before the response
    59  	// for the eth_subscribe method.
    60  	subscription := notifier.CreateSubscription()
    61  
    62  	go func() {
    63  		// test expects n events, if we begin sending event immediately some events
    64  		// will probably be dropped since the subscription ID might not be send to
    65  		// the client.
    66  		for i := 0; i < n; i++ {
    67  			if err := notifier.Notify(subscription.ID, val+i); err != nil {
    68  				return
    69  			}
    70  		}
    71  
    72  		select {
    73  		case <-notifier.Closed():
    74  		case <-subscription.Err():
    75  		}
    76  		if s.unsubscribed != nil {
    77  			s.unsubscribed <- string(subscription.ID)
    78  		}
    79  	}()
    80  
    81  	return subscription, nil
    82  }
    83  
    84  // HangSubscription blocks on s.unblockHangSubscription before
    85  // sending anything.
    86  func (s *NotificationTestService) HangSubscription(ctx context.Context, val int) (*Subscription, error) {
    87  	notifier, supported := NotifierFromContext(ctx)
    88  	if !supported {
    89  		return nil, ErrNotificationsUnsupported
    90  	}
    91  
    92  	s.gotHangSubscriptionReq <- struct{}{}
    93  	<-s.unblockHangSubscription
    94  	subscription := notifier.CreateSubscription()
    95  
    96  	go func() {
    97  		notifier.Notify(subscription.ID, val)
    98  	}()
    99  	return subscription, nil
   100  }
   101  
   102  func TestNewID(t *testing.T) {
   103  	hexchars := "0123456789ABCDEFabcdef"
   104  	for i := 0; i < 100; i++ {
   105  		id := string(NewID())
   106  		if !strings.HasPrefix(id, "0x") {
   107  			t.Fatalf("invalid ID prefix, want '0x...', got %s", id)
   108  		}
   109  
   110  		id = id[2:]
   111  		if len(id) == 0 || len(id) > 32 {
   112  			t.Fatalf("invalid ID length, want len(id) > 0 && len(id) <= 32), got %d", len(id))
   113  		}
   114  
   115  		for i := 0; i < len(id); i++ {
   116  			if strings.IndexByte(hexchars, id[i]) == -1 {
   117  				t.Fatalf("unexpected byte, want any valid hex char, got %c", id[i])
   118  			}
   119  		}
   120  	}
   121  }
   122  
   123  func TestNotifications(t *testing.T) {
   124  	server := NewServer()
   125  	service := &NotificationTestService{unsubscribed: make(chan string)}
   126  
   127  	if err := server.RegisterName("klay", service); err != nil {
   128  		t.Fatalf("unable to register test service %v", err)
   129  	}
   130  
   131  	clientConn, serverConn := net.Pipe()
   132  
   133  	go server.ServeCodec(NewCodec(serverConn), OptionMethodInvocation|OptionSubscriptions)
   134  
   135  	out := json.NewEncoder(clientConn)
   136  	in := json.NewDecoder(clientConn)
   137  
   138  	n := 5
   139  	val := 12345
   140  	request := map[string]interface{}{
   141  		"id":      1,
   142  		"method":  "klay_subscribe",
   143  		"version": "2.0",
   144  		"params":  []interface{}{"someSubscription", n, val},
   145  	}
   146  
   147  	// create subscription
   148  	if err := out.Encode(request); err != nil {
   149  		t.Fatal(err)
   150  	}
   151  
   152  	var subid string
   153  	response := jsonSuccessResponse{Result: subid}
   154  	if err := in.Decode(&response); err != nil {
   155  		t.Fatal(err)
   156  	}
   157  
   158  	var ok bool
   159  	if _, ok = response.Result.(string); !ok {
   160  		t.Fatalf("expected subscription id, got %T", response.Result)
   161  	}
   162  
   163  	for i := 0; i < n; i++ {
   164  		var notification jsonNotification
   165  		if err := in.Decode(&notification); err != nil {
   166  			t.Fatalf("%v", err)
   167  		}
   168  
   169  		if int(notification.Params.Result.(float64)) != val+i {
   170  			t.Fatalf("expected %d, got %d", val+i, notification.Params.Result)
   171  		}
   172  	}
   173  
   174  	clientConn.Close() // causes notification unsubscribe callback to be called
   175  	select {
   176  	case <-service.unsubscribed:
   177  	case <-time.After(1 * time.Second):
   178  		t.Fatal("Unsubscribe not called after one second")
   179  	}
   180  }
   181  
   182  func waitForMessages(t *testing.T, in *json.Decoder, successes chan<- jsonSuccessResponse,
   183  	failures chan<- jsonErrResponse, notifications chan<- jsonNotification, errors chan<- error,
   184  ) {
   185  	// read and parse server messages
   186  	for {
   187  		var rmsg json.RawMessage
   188  		if err := in.Decode(&rmsg); err != nil {
   189  			return
   190  		}
   191  
   192  		var responses []map[string]interface{}
   193  		if rmsg[0] == '[' {
   194  			if err := json.Unmarshal(rmsg, &responses); err != nil {
   195  				errors <- fmt.Errorf("Received invalid message: %s", rmsg)
   196  				return
   197  			}
   198  		} else {
   199  			var msg map[string]interface{}
   200  			if err := json.Unmarshal(rmsg, &msg); err != nil {
   201  				errors <- fmt.Errorf("Received invalid message: %s", rmsg)
   202  				return
   203  			}
   204  			responses = append(responses, msg)
   205  		}
   206  
   207  		for _, msg := range responses {
   208  			// determine what kind of msg was received and broadcast
   209  			// it to over the corresponding channel
   210  			if _, found := msg["result"]; found {
   211  				successes <- jsonSuccessResponse{
   212  					Version: msg["jsonrpc"].(string),
   213  					Id:      msg["id"],
   214  					Result:  msg["result"],
   215  				}
   216  				continue
   217  			}
   218  			if _, found := msg["error"]; found {
   219  				params := msg["params"].(map[string]interface{})
   220  				failures <- jsonErrResponse{
   221  					Version: msg["jsonrpc"].(string),
   222  					Id:      msg["id"],
   223  					Error:   jsonError{int(params["subscription"].(float64)), params["message"].(string), params["data"]},
   224  				}
   225  				continue
   226  			}
   227  			if _, found := msg["params"]; found {
   228  				params := msg["params"].(map[string]interface{})
   229  				notifications <- jsonNotification{
   230  					Version: msg["jsonrpc"].(string),
   231  					Method:  msg["method"].(string),
   232  					Params:  jsonSubscription{params["subscription"].(string), params["result"]},
   233  				}
   234  				continue
   235  			}
   236  			errors <- fmt.Errorf("Received invalid message: %s", msg)
   237  		}
   238  	}
   239  }
   240  
   241  // TestSubscriptionMultipleNamespaces ensures that subscriptions can exists
   242  // for multiple different namespaces.
   243  func TestSubscriptionMultipleNamespaces(t *testing.T) {
   244  	oldMaxSubscription := MaxSubscriptionPerWSConn
   245  	MaxSubscriptionPerWSConn = 100
   246  	defer func() {
   247  		MaxSubscriptionPerWSConn = oldMaxSubscription
   248  	}()
   249  
   250  	var (
   251  		namespaces        = []string{"klay", "shh", "bzz"}
   252  		service           = NotificationTestService{}
   253  		subCount          = len(namespaces) * 2
   254  		notificationCount = 3
   255  
   256  		server                 = NewServer()
   257  		clientConn, serverConn = net.Pipe()
   258  		out                    = json.NewEncoder(clientConn)
   259  		in                     = json.NewDecoder(clientConn)
   260  		successes              = make(chan jsonSuccessResponse)
   261  		failures               = make(chan jsonErrResponse)
   262  		notifications          = make(chan jsonNotification)
   263  		errors                 = make(chan error, 10)
   264  	)
   265  
   266  	// setup and start server
   267  	for _, namespace := range namespaces {
   268  		if err := server.RegisterName(namespace, &service); err != nil {
   269  			t.Fatalf("unable to register test service %v", err)
   270  		}
   271  	}
   272  
   273  	go server.ServeCodec(NewCodec(serverConn), OptionMethodInvocation|OptionSubscriptions)
   274  	defer server.Stop()
   275  
   276  	// wait for message and write them to the given channels
   277  	go waitForMessages(t, in, successes, failures, notifications, errors)
   278  
   279  	// create subscriptions one by one
   280  	for i, namespace := range namespaces {
   281  		request := map[string]interface{}{
   282  			"id":      i,
   283  			"method":  fmt.Sprintf("%s_subscribe", namespace),
   284  			"version": "2.0",
   285  			"params":  []interface{}{"someSubscription", notificationCount, i},
   286  		}
   287  
   288  		if err := out.Encode(&request); err != nil {
   289  			t.Fatalf("Could not create subscription: %v", err)
   290  		}
   291  	}
   292  
   293  	// create all subscriptions in 1 batch
   294  	var requests []interface{}
   295  	for i, namespace := range namespaces {
   296  		requests = append(requests, map[string]interface{}{
   297  			"id":      i,
   298  			"method":  fmt.Sprintf("%s_subscribe", namespace),
   299  			"version": "2.0",
   300  			"params":  []interface{}{"someSubscription", notificationCount, i},
   301  		})
   302  	}
   303  
   304  	if err := out.Encode(&requests); err != nil {
   305  		t.Fatalf("Could not create subscription in batch form: %v", err)
   306  	}
   307  
   308  	timeout := time.After(30 * time.Second)
   309  	subids := make(map[string]string, subCount)
   310  	count := make(map[string]int, subCount)
   311  	allReceived := func() bool {
   312  		done := len(count) == subCount
   313  		for _, c := range count {
   314  			if c < notificationCount {
   315  				done = false
   316  			}
   317  		}
   318  		return done
   319  	}
   320  	for !allReceived() {
   321  		select {
   322  		case suc := <-successes: // subscription created
   323  			subids[namespaces[int(suc.Id.(float64))]] = suc.Result.(string)
   324  		case notification := <-notifications:
   325  			count[notification.Params.Subscription]++
   326  		case err := <-errors:
   327  			t.Fatal(err)
   328  		case failure := <-failures:
   329  			t.Errorf("received error: %v", failure.Error)
   330  		case <-timeout:
   331  			for _, namespace := range namespaces {
   332  				subid, found := subids[namespace]
   333  				if !found {
   334  					t.Errorf("subscription for %q not created", namespace)
   335  					continue
   336  				}
   337  				if count, found := count[subid]; !found || count < notificationCount {
   338  					t.Errorf("didn't receive all notifications (%d<%d) in time for namespace %q", count, notificationCount, namespace)
   339  				}
   340  			}
   341  			t.Fatal("timed out")
   342  		}
   343  	}
   344  }