github.com/cxptek/go-ethereum@v1.9.7/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 "encoding/json" 21 "fmt" 22 "net" 23 "strings" 24 "testing" 25 "time" 26 ) 27 28 func TestNewID(t *testing.T) { 29 hexchars := "0123456789ABCDEFabcdef" 30 for i := 0; i < 100; i++ { 31 id := string(NewID()) 32 if !strings.HasPrefix(id, "0x") { 33 t.Fatalf("invalid ID prefix, want '0x...', got %s", id) 34 } 35 36 id = id[2:] 37 if len(id) == 0 || len(id) > 32 { 38 t.Fatalf("invalid ID length, want len(id) > 0 && len(id) <= 32), got %d", len(id)) 39 } 40 41 for i := 0; i < len(id); i++ { 42 if strings.IndexByte(hexchars, id[i]) == -1 { 43 t.Fatalf("unexpected byte, want any valid hex char, got %c", id[i]) 44 } 45 } 46 } 47 } 48 49 func TestSubscriptions(t *testing.T) { 50 var ( 51 namespaces = []string{"eth", "shh", "bzz"} 52 service = ¬ificationTestService{} 53 subCount = len(namespaces) 54 notificationCount = 3 55 56 server = NewServer() 57 clientConn, serverConn = net.Pipe() 58 out = json.NewEncoder(clientConn) 59 in = json.NewDecoder(clientConn) 60 successes = make(chan subConfirmation) 61 notifications = make(chan subscriptionResult) 62 errors = make(chan error, subCount*notificationCount+1) 63 ) 64 65 // setup and start server 66 for _, namespace := range namespaces { 67 if err := server.RegisterName(namespace, service); err != nil { 68 t.Fatalf("unable to register test service %v", err) 69 } 70 } 71 go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation|OptionSubscriptions) 72 defer server.Stop() 73 74 // wait for message and write them to the given channels 75 go waitForMessages(in, successes, notifications, errors) 76 77 // create subscriptions one by one 78 for i, namespace := range namespaces { 79 request := map[string]interface{}{ 80 "id": i, 81 "method": fmt.Sprintf("%s_subscribe", namespace), 82 "version": "2.0", 83 "params": []interface{}{"someSubscription", notificationCount, i}, 84 } 85 if err := out.Encode(&request); err != nil { 86 t.Fatalf("Could not create subscription: %v", err) 87 } 88 } 89 90 timeout := time.After(30 * time.Second) 91 subids := make(map[string]string, subCount) 92 count := make(map[string]int, subCount) 93 allReceived := func() bool { 94 done := len(count) == subCount 95 for _, c := range count { 96 if c < notificationCount { 97 done = false 98 } 99 } 100 return done 101 } 102 for !allReceived() { 103 select { 104 case confirmation := <-successes: // subscription created 105 subids[namespaces[confirmation.reqid]] = string(confirmation.subid) 106 case notification := <-notifications: 107 count[notification.ID]++ 108 case err := <-errors: 109 t.Fatal(err) 110 case <-timeout: 111 for _, namespace := range namespaces { 112 subid, found := subids[namespace] 113 if !found { 114 t.Errorf("subscription for %q not created", namespace) 115 continue 116 } 117 if count, found := count[subid]; !found || count < notificationCount { 118 t.Errorf("didn't receive all notifications (%d<%d) in time for namespace %q", count, notificationCount, namespace) 119 } 120 } 121 t.Fatal("timed out") 122 } 123 } 124 } 125 126 // This test checks that unsubscribing works. 127 func TestServerUnsubscribe(t *testing.T) { 128 // Start the server. 129 server := newTestServer() 130 service := ¬ificationTestService{unsubscribed: make(chan string)} 131 server.RegisterName("nftest2", service) 132 p1, p2 := net.Pipe() 133 go server.ServeCodec(NewJSONCodec(p1), OptionMethodInvocation|OptionSubscriptions) 134 135 p2.SetDeadline(time.Now().Add(10 * time.Second)) 136 137 // Subscribe. 138 p2.Write([]byte(`{"jsonrpc":"2.0","id":1,"method":"nftest2_subscribe","params":["someSubscription",0,10]}`)) 139 140 // Handle received messages. 141 resps := make(chan subConfirmation) 142 notifications := make(chan subscriptionResult) 143 errors := make(chan error) 144 go waitForMessages(json.NewDecoder(p2), resps, notifications, errors) 145 146 // Receive the subscription ID. 147 var sub subConfirmation 148 select { 149 case sub = <-resps: 150 case err := <-errors: 151 t.Fatal(err) 152 } 153 154 // Unsubscribe and check that it is handled on the server side. 155 p2.Write([]byte(`{"jsonrpc":"2.0","method":"nftest2_unsubscribe","params":["` + sub.subid + `"]}`)) 156 for { 157 select { 158 case id := <-service.unsubscribed: 159 if id != string(sub.subid) { 160 t.Errorf("wrong subscription ID unsubscribed") 161 } 162 return 163 case err := <-errors: 164 t.Fatal(err) 165 case <-notifications: 166 // drop notifications 167 } 168 } 169 } 170 171 type subConfirmation struct { 172 reqid int 173 subid ID 174 } 175 176 func waitForMessages(in *json.Decoder, successes chan subConfirmation, notifications chan subscriptionResult, errors chan error) { 177 for { 178 var msg jsonrpcMessage 179 if err := in.Decode(&msg); err != nil { 180 errors <- fmt.Errorf("decode error: %v", err) 181 return 182 } 183 switch { 184 case msg.isNotification(): 185 var res subscriptionResult 186 if err := json.Unmarshal(msg.Params, &res); err != nil { 187 errors <- fmt.Errorf("invalid subscription result: %v", err) 188 } else { 189 notifications <- res 190 } 191 case msg.isResponse(): 192 var c subConfirmation 193 if msg.Error != nil { 194 errors <- msg.Error 195 } else if err := json.Unmarshal(msg.Result, &c.subid); err != nil { 196 errors <- fmt.Errorf("invalid response: %v", err) 197 } else { 198 json.Unmarshal(msg.ID, &c.reqid) 199 successes <- c 200 } 201 default: 202 errors <- fmt.Errorf("unrecognized message: %v", msg) 203 return 204 } 205 } 206 }