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(¬ification); 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 }