github.com/MetalBlockchain/subnet-evm@v0.4.9/rpc/subscription.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2016 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package rpc 28 29 import ( 30 "container/list" 31 "context" 32 crand "crypto/rand" 33 "encoding/binary" 34 "encoding/hex" 35 "encoding/json" 36 "errors" 37 "math/rand" 38 "reflect" 39 "strings" 40 "sync" 41 "time" 42 ) 43 44 var ( 45 // ErrNotificationsUnsupported is returned when the connection doesn't support notifications 46 ErrNotificationsUnsupported = errors.New("notifications not supported") 47 // ErrSubscriptionNotFound is returned when the notification for the given id is not found 48 ErrSubscriptionNotFound = errors.New("subscription not found") 49 ) 50 51 var globalGen = randomIDGenerator() 52 53 // ID defines a pseudo random number that is used to identify RPC subscriptions. 54 type ID string 55 56 // NewID returns a new, random ID. 57 func NewID() ID { 58 return globalGen() 59 } 60 61 // randomIDGenerator returns a function generates a random IDs. 62 func randomIDGenerator() func() ID { 63 var buf = make([]byte, 8) 64 var seed int64 65 if _, err := crand.Read(buf); err == nil { 66 seed = int64(binary.BigEndian.Uint64(buf)) 67 } else { 68 seed = int64(time.Now().Nanosecond()) 69 } 70 71 var ( 72 mu sync.Mutex 73 rng = rand.New(rand.NewSource(seed)) 74 ) 75 return func() ID { 76 mu.Lock() 77 defer mu.Unlock() 78 id := make([]byte, 16) 79 rng.Read(id) 80 return encodeID(id) 81 } 82 } 83 84 func encodeID(b []byte) ID { 85 id := hex.EncodeToString(b) 86 id = strings.TrimLeft(id, "0") 87 if id == "" { 88 id = "0" // ID's are RPC quantities, no leading zero's and 0 is 0x0. 89 } 90 return ID("0x" + id) 91 } 92 93 type notifierKey struct{} 94 95 // NotifierFromContext returns the Notifier value stored in ctx, if any. 96 func NotifierFromContext(ctx context.Context) (*Notifier, bool) { 97 n, ok := ctx.Value(notifierKey{}).(*Notifier) 98 return n, ok 99 } 100 101 // Notifier is tied to a RPC connection that supports subscriptions. 102 // Server callbacks use the notifier to send notifications. 103 type Notifier struct { 104 h *handler 105 namespace string 106 107 mu sync.Mutex 108 sub *Subscription 109 buffer []json.RawMessage 110 callReturned bool 111 activated bool 112 } 113 114 // CreateSubscription returns a new subscription that is coupled to the 115 // RPC connection. By default subscriptions are inactive and notifications 116 // are dropped until the subscription is marked as active. This is done 117 // by the RPC server after the subscription ID is send to the client. 118 func (n *Notifier) CreateSubscription() *Subscription { 119 n.mu.Lock() 120 defer n.mu.Unlock() 121 122 if n.sub != nil { 123 panic("can't create multiple subscriptions with Notifier") 124 } else if n.callReturned { 125 panic("can't create subscription after subscribe call has returned") 126 } 127 n.sub = &Subscription{ID: n.h.idgen(), namespace: n.namespace, err: make(chan error, 1)} 128 return n.sub 129 } 130 131 // Notify sends a notification to the client with the given data as payload. 132 // If an error occurs the RPC connection is closed and the error is returned. 133 func (n *Notifier) Notify(id ID, data interface{}) error { 134 enc, err := json.Marshal(data) 135 if err != nil { 136 return err 137 } 138 139 n.mu.Lock() 140 defer n.mu.Unlock() 141 142 if n.sub == nil { 143 panic("can't Notify before subscription is created") 144 } else if n.sub.ID != id { 145 panic("Notify with wrong ID") 146 } 147 if n.activated { 148 return n.send(n.sub, enc) 149 } 150 n.buffer = append(n.buffer, enc) 151 return nil 152 } 153 154 // Closed returns a channel that is closed when the RPC connection is closed. 155 // Deprecated: use subscription error channel 156 func (n *Notifier) Closed() <-chan interface{} { 157 return n.h.conn.closed() 158 } 159 160 // takeSubscription returns the subscription (if one has been created). No subscription can 161 // be created after this call. 162 func (n *Notifier) takeSubscription() *Subscription { 163 n.mu.Lock() 164 defer n.mu.Unlock() 165 n.callReturned = true 166 return n.sub 167 } 168 169 // activate is called after the subscription ID was sent to client. Notifications are 170 // buffered before activation. This prevents notifications being sent to the client before 171 // the subscription ID is sent to the client. 172 func (n *Notifier) activate() error { 173 n.mu.Lock() 174 defer n.mu.Unlock() 175 176 for _, data := range n.buffer { 177 if err := n.send(n.sub, data); err != nil { 178 return err 179 } 180 } 181 n.activated = true 182 return nil 183 } 184 185 func (n *Notifier) send(sub *Subscription, data json.RawMessage) error { 186 params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data}) 187 ctx := context.Background() 188 return n.h.conn.writeJSON(ctx, &jsonrpcMessage{ 189 Version: vsn, 190 Method: n.namespace + notificationMethodSuffix, 191 Params: params, 192 }) 193 } 194 195 // A Subscription is created by a notifier and tied to that notifier. The client can use 196 // this subscription to wait for an unsubscribe request for the client, see Err(). 197 type Subscription struct { 198 ID ID 199 namespace string 200 err chan error // closed on unsubscribe 201 } 202 203 // Err returns a channel that is closed when the client send an unsubscribe request. 204 func (s *Subscription) Err() <-chan error { 205 return s.err 206 } 207 208 // MarshalJSON marshals a subscription as its ID. 209 func (s *Subscription) MarshalJSON() ([]byte, error) { 210 return json.Marshal(s.ID) 211 } 212 213 // ClientSubscription is a subscription established through the Client's Subscribe or 214 // EthSubscribe methods. 215 type ClientSubscription struct { 216 client *Client 217 etype reflect.Type 218 channel reflect.Value 219 namespace string 220 subid string 221 222 // The in channel receives notification values from client dispatcher. 223 in chan json.RawMessage 224 225 // The error channel receives the error from the forwarding loop. 226 // It is closed by Unsubscribe. 227 err chan error 228 errOnce sync.Once 229 230 // Closing of the subscription is requested by sending on 'quit'. This is handled by 231 // the forwarding loop, which closes 'forwardDone' when it has stopped sending to 232 // sub.channel. Finally, 'unsubDone' is closed after unsubscribing on the server side. 233 quit chan error 234 forwardDone chan struct{} 235 unsubDone chan struct{} 236 } 237 238 // This is the sentinel value sent on sub.quit when Unsubscribe is called. 239 var errUnsubscribed = errors.New("unsubscribed") 240 241 func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription { 242 sub := &ClientSubscription{ 243 client: c, 244 namespace: namespace, 245 etype: channel.Type().Elem(), 246 channel: channel, 247 in: make(chan json.RawMessage), 248 quit: make(chan error), 249 forwardDone: make(chan struct{}), 250 unsubDone: make(chan struct{}), 251 err: make(chan error, 1), 252 } 253 return sub 254 } 255 256 // Err returns the subscription error channel. The intended use of Err is to schedule 257 // resubscription when the client connection is closed unexpectedly. 258 // 259 // The error channel receives a value when the subscription has ended due to an error. The 260 // received error is nil if Close has been called on the underlying client and no other 261 // error has occurred. 262 // 263 // The error channel is closed when Unsubscribe is called on the subscription. 264 func (sub *ClientSubscription) Err() <-chan error { 265 return sub.err 266 } 267 268 // Unsubscribe unsubscribes the notification and closes the error channel. 269 // It can safely be called more than once. 270 func (sub *ClientSubscription) Unsubscribe() { 271 sub.errOnce.Do(func() { 272 select { 273 case sub.quit <- errUnsubscribed: 274 <-sub.unsubDone 275 case <-sub.unsubDone: 276 } 277 close(sub.err) 278 }) 279 } 280 281 // deliver is called by the client's message dispatcher to send a notification value. 282 func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) { 283 select { 284 case sub.in <- result: 285 return true 286 case <-sub.forwardDone: 287 return false 288 } 289 } 290 291 // close is called by the client's message dispatcher when the connection is closed. 292 func (sub *ClientSubscription) close(err error) { 293 select { 294 case sub.quit <- err: 295 case <-sub.forwardDone: 296 } 297 } 298 299 // run is the forwarding loop of the subscription. It runs in its own goroutine and 300 // is launched by the client's handler after the subscription has been created. 301 func (sub *ClientSubscription) run() { 302 defer close(sub.unsubDone) 303 304 unsubscribe, err := sub.forward() 305 306 // The client's dispatch loop won't be able to execute the unsubscribe call if it is 307 // blocked in sub.deliver() or sub.close(). Closing forwardDone unblocks them. 308 close(sub.forwardDone) 309 310 // Call the unsubscribe method on the server. 311 if unsubscribe { 312 sub.requestUnsubscribe() 313 } 314 315 // Send the error. 316 if err != nil { 317 if err == ErrClientQuit { 318 // ErrClientQuit gets here when Client.Close is called. This is reported as a 319 // nil error because it's not an error, but we can't close sub.err here. 320 err = nil 321 } 322 sub.err <- err 323 } 324 } 325 326 // forward is the forwarding loop. It takes in RPC notifications and sends them 327 // on the subscription channel. 328 func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { 329 cases := []reflect.SelectCase{ 330 {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)}, 331 {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)}, 332 {Dir: reflect.SelectSend, Chan: sub.channel}, 333 } 334 buffer := list.New() 335 336 for { 337 var chosen int 338 var recv reflect.Value 339 if buffer.Len() == 0 { 340 // Idle, omit send case. 341 chosen, recv, _ = reflect.Select(cases[:2]) 342 } else { 343 // Non-empty buffer, send the first queued item. 344 cases[2].Send = reflect.ValueOf(buffer.Front().Value) 345 chosen, recv, _ = reflect.Select(cases) 346 } 347 348 switch chosen { 349 case 0: // <-sub.quit 350 if !recv.IsNil() { 351 err = recv.Interface().(error) 352 } 353 if err == errUnsubscribed { 354 // Exiting because Unsubscribe was called, unsubscribe on server. 355 return true, nil 356 } 357 return false, err 358 359 case 1: // <-sub.in 360 val, err := sub.unmarshal(recv.Interface().(json.RawMessage)) 361 if err != nil { 362 return true, err 363 } 364 if buffer.Len() == maxClientSubscriptionBuffer { 365 return true, ErrSubscriptionQueueOverflow 366 } 367 buffer.PushBack(val) 368 369 case 2: // sub.channel<- 370 cases[2].Send = reflect.Value{} // Don't hold onto the value. 371 buffer.Remove(buffer.Front()) 372 } 373 } 374 } 375 376 func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) { 377 val := reflect.New(sub.etype) 378 err := json.Unmarshal(result, val.Interface()) 379 return val.Elem().Interface(), err 380 } 381 382 func (sub *ClientSubscription) requestUnsubscribe() error { 383 var result interface{} 384 return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid) 385 }