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