github.com/rumhocker/blockbook@v0.3.2/server/websocket.go (about) 1 package server 2 3 import ( 4 "blockbook/api" 5 "blockbook/bchain" 6 "blockbook/common" 7 "blockbook/db" 8 "encoding/json" 9 "math/big" 10 "net/http" 11 "runtime/debug" 12 "strconv" 13 "strings" 14 "sync" 15 "sync/atomic" 16 "time" 17 18 "github.com/golang/glog" 19 "github.com/gorilla/websocket" 20 "github.com/juju/errors" 21 ) 22 23 const upgradeFailed = "Upgrade failed: " 24 const outChannelSize = 500 25 const defaultTimeout = 60 * time.Second 26 27 // allRates is a special "currency" parameter that means all available currencies 28 const allFiatRates = "!ALL!" 29 30 var ( 31 // ErrorMethodNotAllowed is returned when client tries to upgrade method other than GET 32 ErrorMethodNotAllowed = errors.New("Method not allowed") 33 34 connectionCounter uint64 35 ) 36 37 type websocketReq struct { 38 ID string `json:"id"` 39 Method string `json:"method"` 40 Params json.RawMessage `json:"params"` 41 } 42 43 type websocketRes struct { 44 ID string `json:"id"` 45 Data interface{} `json:"data"` 46 } 47 48 type websocketChannel struct { 49 id uint64 50 conn *websocket.Conn 51 out chan *websocketRes 52 ip string 53 requestHeader http.Header 54 alive bool 55 aliveLock sync.Mutex 56 } 57 58 // WebsocketServer is a handle to websocket server 59 type WebsocketServer struct { 60 socket *websocket.Conn 61 upgrader *websocket.Upgrader 62 db *db.RocksDB 63 txCache *db.TxCache 64 chain bchain.BlockChain 65 chainParser bchain.BlockChainParser 66 mempool bchain.Mempool 67 metrics *common.Metrics 68 is *common.InternalState 69 api *api.Worker 70 block0hash string 71 newBlockSubscriptions map[*websocketChannel]string 72 newBlockSubscriptionsLock sync.Mutex 73 addressSubscriptions map[string]map[*websocketChannel]string 74 addressSubscriptionsLock sync.Mutex 75 fiatRatesSubscriptions map[string]map[*websocketChannel]string 76 fiatRatesSubscriptionsLock sync.Mutex 77 } 78 79 // NewWebsocketServer creates new websocket interface to blockbook and returns its handle 80 func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*WebsocketServer, error) { 81 api, err := api.NewWorker(db, chain, mempool, txCache, is) 82 if err != nil { 83 return nil, err 84 } 85 b0, err := db.GetBlockHash(0) 86 if err != nil { 87 return nil, err 88 } 89 s := &WebsocketServer{ 90 upgrader: &websocket.Upgrader{ 91 ReadBufferSize: 1024 * 32, 92 WriteBufferSize: 1024 * 32, 93 CheckOrigin: checkOrigin, 94 }, 95 db: db, 96 txCache: txCache, 97 chain: chain, 98 chainParser: chain.GetChainParser(), 99 mempool: mempool, 100 metrics: metrics, 101 is: is, 102 api: api, 103 block0hash: b0, 104 newBlockSubscriptions: make(map[*websocketChannel]string), 105 addressSubscriptions: make(map[string]map[*websocketChannel]string), 106 fiatRatesSubscriptions: make(map[string]map[*websocketChannel]string), 107 } 108 return s, nil 109 } 110 111 // allow all origins 112 func checkOrigin(r *http.Request) bool { 113 return true 114 } 115 116 // ServeHTTP sets up handler of websocket channel 117 func (s *WebsocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 118 if r.Method != "GET" { 119 http.Error(w, upgradeFailed+ErrorMethodNotAllowed.Error(), 503) 120 return 121 } 122 conn, err := s.upgrader.Upgrade(w, r, nil) 123 if err != nil { 124 http.Error(w, upgradeFailed+err.Error(), 503) 125 return 126 } 127 c := &websocketChannel{ 128 id: atomic.AddUint64(&connectionCounter, 1), 129 conn: conn, 130 out: make(chan *websocketRes, outChannelSize), 131 ip: r.RemoteAddr, 132 requestHeader: r.Header, 133 alive: true, 134 } 135 go s.inputLoop(c) 136 go s.outputLoop(c) 137 s.onConnect(c) 138 } 139 140 // GetHandler returns http handler 141 func (s *WebsocketServer) GetHandler() http.Handler { 142 return s 143 } 144 145 func (s *WebsocketServer) closeChannel(c *websocketChannel) { 146 c.aliveLock.Lock() 147 defer c.aliveLock.Unlock() 148 if c.alive { 149 c.conn.Close() 150 c.alive = false 151 //clean out 152 close(c.out) 153 for len(c.out) > 0 { 154 <-c.out 155 } 156 s.onDisconnect(c) 157 } 158 } 159 160 func (c *websocketChannel) IsAlive() bool { 161 c.aliveLock.Lock() 162 defer c.aliveLock.Unlock() 163 return c.alive 164 } 165 166 func (s *WebsocketServer) inputLoop(c *websocketChannel) { 167 defer func() { 168 if r := recover(); r != nil { 169 glog.Error("recovered from panic: ", r, ", ", c.id) 170 debug.PrintStack() 171 s.closeChannel(c) 172 } 173 }() 174 for { 175 t, d, err := c.conn.ReadMessage() 176 if err != nil { 177 s.closeChannel(c) 178 return 179 } 180 switch t { 181 case websocket.TextMessage: 182 var req websocketReq 183 err := json.Unmarshal(d, &req) 184 if err != nil { 185 glog.Error("Error parsing message from ", c.id, ", ", string(d), ", ", err) 186 s.closeChannel(c) 187 return 188 } 189 go s.onRequest(c, &req) 190 case websocket.BinaryMessage: 191 glog.Error("Binary message received from ", c.id, ", ", c.ip) 192 s.closeChannel(c) 193 return 194 case websocket.PingMessage: 195 c.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(defaultTimeout)) 196 break 197 case websocket.CloseMessage: 198 s.closeChannel(c) 199 return 200 case websocket.PongMessage: 201 // do nothing 202 } 203 } 204 } 205 206 func (s *WebsocketServer) outputLoop(c *websocketChannel) { 207 for m := range c.out { 208 err := c.conn.WriteJSON(m) 209 if err != nil { 210 glog.Error("Error sending message to ", c.id, ", ", err) 211 s.closeChannel(c) 212 } 213 } 214 } 215 216 func (s *WebsocketServer) onConnect(c *websocketChannel) { 217 glog.Info("Client connected ", c.id, ", ", c.ip) 218 s.metrics.WebsocketClients.Inc() 219 } 220 221 func (s *WebsocketServer) onDisconnect(c *websocketChannel) { 222 s.unsubscribeNewBlock(c) 223 s.unsubscribeAddresses(c) 224 s.unsubscribeFiatRates(c) 225 glog.Info("Client disconnected ", c.id, ", ", c.ip) 226 s.metrics.WebsocketClients.Dec() 227 } 228 229 var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *websocketReq) (interface{}, error){ 230 "getAccountInfo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 231 r, err := unmarshalGetAccountInfoRequest(req.Params) 232 if err == nil { 233 rv, err = s.getAccountInfo(r) 234 } 235 return 236 }, 237 "getInfo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 238 return s.getInfo() 239 }, 240 "getBlockHash": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 241 r := struct { 242 Height int `json:"height"` 243 }{} 244 err = json.Unmarshal(req.Params, &r) 245 if err == nil { 246 rv, err = s.getBlockHash(r.Height) 247 } 248 return 249 }, 250 "getAccountUtxo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 251 r := struct { 252 Descriptor string `json:"descriptor"` 253 }{} 254 err = json.Unmarshal(req.Params, &r) 255 if err == nil { 256 rv, err = s.getAccountUtxo(r.Descriptor) 257 } 258 return 259 }, 260 "getBalanceHistory": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 261 r := struct { 262 Descriptor string `json:"descriptor"` 263 From int64 `json:"from"` 264 To int64 `json:"to"` 265 Currencies []string `json:"currencies"` 266 Gap int `json:"gap"` 267 GroupBy uint32 `json:"groupBy"` 268 }{} 269 err = json.Unmarshal(req.Params, &r) 270 if err == nil { 271 if r.From <= 0 { 272 r.From = 0 273 } 274 if r.To <= 0 { 275 r.To = 0 276 } 277 if r.GroupBy <= 0 { 278 r.GroupBy = 3600 279 } 280 rv, err = s.api.GetXpubBalanceHistory(r.Descriptor, r.From, r.To, r.Currencies, r.Gap, r.GroupBy) 281 if err != nil { 282 rv, err = s.api.GetBalanceHistory(r.Descriptor, r.From, r.To, r.Currencies, r.GroupBy) 283 } 284 } 285 return 286 }, 287 "getTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 288 r := struct { 289 Txid string `json:"txid"` 290 }{} 291 err = json.Unmarshal(req.Params, &r) 292 if err == nil { 293 rv, err = s.getTransaction(r.Txid) 294 } 295 return 296 }, 297 "getTransactionSpecific": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 298 r := struct { 299 Txid string `json:"txid"` 300 }{} 301 err = json.Unmarshal(req.Params, &r) 302 if err == nil { 303 rv, err = s.getTransactionSpecific(r.Txid) 304 } 305 return 306 }, 307 "estimateFee": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 308 return s.estimateFee(c, req.Params) 309 }, 310 "sendTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 311 r := struct { 312 Hex string `json:"hex"` 313 }{} 314 err = json.Unmarshal(req.Params, &r) 315 if err == nil { 316 rv, err = s.sendTransaction(r.Hex) 317 } 318 return 319 }, 320 "subscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 321 return s.subscribeNewBlock(c, req) 322 }, 323 "unsubscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 324 return s.unsubscribeNewBlock(c) 325 }, 326 "subscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 327 ad, err := s.unmarshalAddresses(req.Params) 328 if err == nil { 329 rv, err = s.subscribeAddresses(c, ad, req) 330 } 331 return 332 }, 333 "unsubscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 334 return s.unsubscribeAddresses(c) 335 }, 336 "subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 337 r := struct { 338 Currency string `json:"currency"` 339 }{} 340 err = json.Unmarshal(req.Params, &r) 341 if err != nil { 342 return nil, err 343 } 344 return s.subscribeFiatRates(c, strings.ToLower(r.Currency), req) 345 }, 346 "unsubscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 347 return s.unsubscribeFiatRates(c) 348 }, 349 "ping": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 350 r := struct{}{} 351 return r, nil 352 }, 353 "getCurrentFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 354 r := struct { 355 Currencies []string `json:"currencies"` 356 }{} 357 err = json.Unmarshal(req.Params, &r) 358 if err == nil { 359 rv, err = s.getCurrentFiatRates(r.Currencies) 360 } 361 return 362 }, 363 "getFiatRatesForTimestamps": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 364 r := struct { 365 Timestamps []int64 `json:"timestamps"` 366 Currencies []string `json:"currencies"` 367 }{} 368 err = json.Unmarshal(req.Params, &r) 369 if err == nil { 370 rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies) 371 } 372 return 373 }, 374 "getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) { 375 r := struct { 376 Timestamp int64 `json:"timestamp"` 377 }{} 378 err = json.Unmarshal(req.Params, &r) 379 if err == nil { 380 rv, err = s.getFiatRatesTickersList(r.Timestamp) 381 } 382 return 383 }, 384 } 385 386 func sendResponse(c *websocketChannel, req *websocketReq, data interface{}) { 387 defer func() { 388 if r := recover(); r != nil { 389 glog.Error("Client ", c.id, ", onRequest ", req.Method, " recovered from panic: ", r) 390 } 391 }() 392 c.out <- &websocketRes{ 393 ID: req.ID, 394 Data: data, 395 } 396 } 397 398 func (s *WebsocketServer) onRequest(c *websocketChannel, req *websocketReq) { 399 var err error 400 var data interface{} 401 defer func() { 402 if r := recover(); r != nil { 403 glog.Error("Client ", c.id, ", onRequest ", req.Method, " recovered from panic: ", r) 404 debug.PrintStack() 405 e := resultError{} 406 e.Error.Message = "Internal error" 407 data = e 408 } 409 // nil data means no response 410 if data != nil { 411 sendResponse(c, req, data) 412 } 413 }() 414 t := time.Now() 415 defer s.metrics.WebsocketReqDuration.With(common.Labels{"method": req.Method}).Observe(float64(time.Since(t)) / 1e3) // in microseconds 416 f, ok := requestHandlers[req.Method] 417 if ok { 418 data, err = f(s, c, req) 419 } else { 420 err = errors.New("unknown method") 421 } 422 if err == nil { 423 glog.V(1).Info("Client ", c.id, " onRequest ", req.Method, " success") 424 s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": "success"}).Inc() 425 } else { 426 glog.Error("Client ", c.id, " onMessage ", req.Method, ": ", errors.ErrorStack(err), ", data ", string(req.Params)) 427 s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": "failure"}).Inc() 428 e := resultError{} 429 e.Error.Message = err.Error() 430 data = e 431 } 432 } 433 434 type accountInfoReq struct { 435 Descriptor string `json:"descriptor"` 436 Details string `json:"details"` 437 Tokens string `json:"tokens"` 438 PageSize int `json:"pageSize"` 439 Page int `json:"page"` 440 FromHeight int `json:"from"` 441 ToHeight int `json:"to"` 442 ContractFilter string `json:"contractFilter"` 443 Gap int `json:"gap"` 444 } 445 446 func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) { 447 var r accountInfoReq 448 err := json.Unmarshal(params, &r) 449 if err != nil { 450 return nil, err 451 } 452 return &r, nil 453 } 454 455 func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) { 456 var opt api.AccountDetails 457 switch req.Details { 458 case "tokens": 459 opt = api.AccountDetailsTokens 460 case "tokenBalances": 461 opt = api.AccountDetailsTokenBalances 462 case "txids": 463 opt = api.AccountDetailsTxidHistory 464 case "txslight": 465 opt = api.AccountDetailsTxHistoryLight 466 case "txs": 467 opt = api.AccountDetailsTxHistory 468 default: 469 opt = api.AccountDetailsBasic 470 } 471 var tokensToReturn api.TokensToReturn 472 switch req.Tokens { 473 case "used": 474 tokensToReturn = api.TokensToReturnUsed 475 case "nonzero": 476 tokensToReturn = api.TokensToReturnNonzeroBalance 477 default: 478 tokensToReturn = api.TokensToReturnDerived 479 } 480 filter := api.AddressFilter{ 481 FromHeight: uint32(req.FromHeight), 482 ToHeight: uint32(req.ToHeight), 483 Contract: req.ContractFilter, 484 Vout: api.AddressFilterVoutOff, 485 TokensToReturn: tokensToReturn, 486 } 487 if req.PageSize == 0 { 488 req.PageSize = txsOnPage 489 } 490 a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, req.Gap) 491 if err != nil { 492 return s.api.GetAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter) 493 } 494 return a, nil 495 } 496 497 func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error) { 498 utxo, err := s.api.GetXpubUtxo(descriptor, false, 0) 499 if err != nil { 500 return s.api.GetAddressUtxo(descriptor, false) 501 } 502 return utxo, nil 503 } 504 505 func (s *WebsocketServer) getTransaction(txid string) (interface{}, error) { 506 return s.api.GetTransaction(txid, false, false) 507 } 508 509 func (s *WebsocketServer) getTransactionSpecific(txid string) (interface{}, error) { 510 return s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid}) 511 } 512 513 func (s *WebsocketServer) getInfo() (interface{}, error) { 514 vi := common.GetVersionInfo() 515 height, hash, err := s.db.GetBestBlock() 516 if err != nil { 517 return nil, err 518 } 519 type info struct { 520 Name string `json:"name"` 521 Shortcut string `json:"shortcut"` 522 Decimals int `json:"decimals"` 523 Version string `json:"version"` 524 BestHeight int `json:"bestHeight"` 525 BestHash string `json:"bestHash"` 526 Block0Hash string `json:"block0Hash"` 527 Testnet bool `json:"testnet"` 528 } 529 return &info{ 530 Name: s.is.Coin, 531 Shortcut: s.is.CoinShortcut, 532 Decimals: s.chainParser.AmountDecimals(), 533 BestHeight: int(height), 534 BestHash: hash, 535 Version: vi.Version, 536 Block0Hash: s.block0hash, 537 Testnet: s.chain.IsTestnet(), 538 }, nil 539 } 540 541 func (s *WebsocketServer) getBlockHash(height int) (interface{}, error) { 542 h, err := s.db.GetBlockHash(uint32(height)) 543 if err != nil { 544 return nil, err 545 } 546 type hash struct { 547 Hash string `json:"hash"` 548 } 549 return &hash{ 550 Hash: h, 551 }, nil 552 } 553 554 func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (interface{}, error) { 555 type estimateFeeReq struct { 556 Blocks []int `json:"blocks"` 557 Specific map[string]interface{} `json:"specific"` 558 } 559 type estimateFeeRes struct { 560 FeePerTx string `json:"feePerTx,omitempty"` 561 FeePerUnit string `json:"feePerUnit,omitempty"` 562 FeeLimit string `json:"feeLimit,omitempty"` 563 } 564 var r estimateFeeReq 565 err := json.Unmarshal(params, &r) 566 if err != nil { 567 return nil, err 568 } 569 res := make([]estimateFeeRes, len(r.Blocks)) 570 if s.chainParser.GetChainType() == bchain.ChainEthereumType { 571 gas, err := s.chain.EthereumTypeEstimateGas(r.Specific) 572 if err != nil { 573 return nil, err 574 } 575 sg := strconv.FormatUint(gas, 10) 576 for i, b := range r.Blocks { 577 fee, err := s.chain.EstimateSmartFee(b, true) 578 if err != nil { 579 return nil, err 580 } 581 res[i].FeePerUnit = fee.String() 582 res[i].FeeLimit = sg 583 fee.Mul(&fee, new(big.Int).SetUint64(gas)) 584 res[i].FeePerTx = fee.String() 585 } 586 } else { 587 conservative := true 588 v, ok := r.Specific["conservative"] 589 if ok { 590 vc, ok := v.(bool) 591 if ok { 592 conservative = vc 593 } 594 } 595 txSize := 0 596 v, ok = r.Specific["txsize"] 597 if ok { 598 f, ok := v.(float64) 599 if ok { 600 txSize = int(f) 601 } 602 } 603 for i, b := range r.Blocks { 604 fee, err := s.chain.EstimateSmartFee(b, conservative) 605 if err != nil { 606 return nil, err 607 } 608 res[i].FeePerUnit = fee.String() 609 if txSize > 0 { 610 fee.Mul(&fee, big.NewInt(int64(txSize))) 611 fee.Add(&fee, big.NewInt(500)) 612 fee.Div(&fee, big.NewInt(1000)) 613 res[i].FeePerTx = fee.String() 614 } 615 } 616 } 617 return res, nil 618 } 619 620 func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction, err error) { 621 txid, err := s.chain.SendRawTransaction(tx) 622 if err != nil { 623 return res, err 624 } 625 res.Result = txid 626 return 627 } 628 629 type subscriptionResponse struct { 630 Subscribed bool `json:"subscribed"` 631 } 632 633 func (s *WebsocketServer) subscribeNewBlock(c *websocketChannel, req *websocketReq) (res interface{}, err error) { 634 s.newBlockSubscriptionsLock.Lock() 635 defer s.newBlockSubscriptionsLock.Unlock() 636 s.newBlockSubscriptions[c] = req.ID 637 return &subscriptionResponse{true}, nil 638 } 639 640 func (s *WebsocketServer) unsubscribeNewBlock(c *websocketChannel) (res interface{}, err error) { 641 s.newBlockSubscriptionsLock.Lock() 642 defer s.newBlockSubscriptionsLock.Unlock() 643 delete(s.newBlockSubscriptions, c) 644 return &subscriptionResponse{false}, nil 645 } 646 647 func (s *WebsocketServer) unmarshalAddresses(params []byte) ([]bchain.AddressDescriptor, error) { 648 r := struct { 649 Addresses []string `json:"addresses"` 650 }{} 651 err := json.Unmarshal(params, &r) 652 if err != nil { 653 return nil, err 654 } 655 rv := make([]bchain.AddressDescriptor, len(r.Addresses)) 656 for i, a := range r.Addresses { 657 ad, err := s.chainParser.GetAddrDescFromAddress(a) 658 if err != nil { 659 return nil, err 660 } 661 rv[i] = ad 662 } 663 return rv, nil 664 } 665 666 func (s *WebsocketServer) subscribeAddresses(c *websocketChannel, addrDesc []bchain.AddressDescriptor, req *websocketReq) (res interface{}, err error) { 667 // unsubscribe all previous subscriptions 668 s.unsubscribeAddresses(c) 669 s.addressSubscriptionsLock.Lock() 670 defer s.addressSubscriptionsLock.Unlock() 671 for i := range addrDesc { 672 ads := string(addrDesc[i]) 673 as, ok := s.addressSubscriptions[ads] 674 if !ok { 675 as = make(map[*websocketChannel]string) 676 s.addressSubscriptions[ads] = as 677 } 678 as[c] = req.ID 679 } 680 return &subscriptionResponse{true}, nil 681 } 682 683 // unsubscribeAddresses unsubscribes all address subscriptions by this channel 684 func (s *WebsocketServer) unsubscribeAddresses(c *websocketChannel) (res interface{}, err error) { 685 s.addressSubscriptionsLock.Lock() 686 defer s.addressSubscriptionsLock.Unlock() 687 for _, sa := range s.addressSubscriptions { 688 for sc := range sa { 689 if sc == c { 690 delete(sa, c) 691 } 692 } 693 } 694 return &subscriptionResponse{false}, nil 695 } 696 697 // subscribeFiatRates subscribes all FiatRates subscriptions by this channel 698 func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, currency string, req *websocketReq) (res interface{}, err error) { 699 // unsubscribe all previous subscriptions 700 s.unsubscribeFiatRates(c) 701 s.fiatRatesSubscriptionsLock.Lock() 702 defer s.fiatRatesSubscriptionsLock.Unlock() 703 704 if currency == "" { 705 currency = allFiatRates 706 } 707 as, ok := s.fiatRatesSubscriptions[currency] 708 if !ok { 709 as = make(map[*websocketChannel]string) 710 s.fiatRatesSubscriptions[currency] = as 711 } 712 as[c] = req.ID 713 return &subscriptionResponse{true}, nil 714 } 715 716 // unsubscribeFiatRates unsubscribes all FiatRates subscriptions by this channel 717 func (s *WebsocketServer) unsubscribeFiatRates(c *websocketChannel) (res interface{}, err error) { 718 s.fiatRatesSubscriptionsLock.Lock() 719 defer s.fiatRatesSubscriptionsLock.Unlock() 720 for _, sa := range s.fiatRatesSubscriptions { 721 for sc := range sa { 722 if sc == c { 723 delete(sa, c) 724 } 725 } 726 } 727 return &subscriptionResponse{false}, nil 728 } 729 730 // OnNewBlock is a callback that broadcasts info about new block to subscribed clients 731 func (s *WebsocketServer) OnNewBlock(hash string, height uint32) { 732 s.newBlockSubscriptionsLock.Lock() 733 defer s.newBlockSubscriptionsLock.Unlock() 734 data := struct { 735 Height uint32 `json:"height"` 736 Hash string `json:"hash"` 737 }{ 738 Height: height, 739 Hash: hash, 740 } 741 for c, id := range s.newBlockSubscriptions { 742 if c.IsAlive() { 743 c.out <- &websocketRes{ 744 ID: id, 745 Data: &data, 746 } 747 } 748 } 749 glog.Info("broadcasting new block ", height, " ", hash, " to ", len(s.newBlockSubscriptions), " channels") 750 } 751 752 // OnNewTxAddr is a callback that broadcasts info about a tx affecting subscribed address 753 func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDescriptor) { 754 // check if there is any subscription but release the lock immediately, GetTransactionFromBchainTx may take some time 755 s.addressSubscriptionsLock.Lock() 756 as, ok := s.addressSubscriptions[string(addrDesc)] 757 lenAs := len(as) 758 s.addressSubscriptionsLock.Unlock() 759 if ok && lenAs > 0 { 760 addr, _, err := s.chainParser.GetAddressesFromAddrDesc(addrDesc) 761 if err != nil { 762 glog.Error("GetAddressesFromAddrDesc error ", err, " for ", addrDesc) 763 return 764 } 765 if len(addr) == 1 { 766 atx, err := s.api.GetTransactionFromBchainTx(tx, 0, false, false) 767 if err != nil { 768 glog.Error("GetTransactionFromBchainTx error ", err, " for ", tx.Txid) 769 return 770 } 771 data := struct { 772 Address string `json:"address"` 773 Tx *api.Tx `json:"tx"` 774 }{ 775 Address: addr[0], 776 Tx: atx, 777 } 778 // get the list of subscriptions again, this time keep the lock 779 s.addressSubscriptionsLock.Lock() 780 defer s.addressSubscriptionsLock.Unlock() 781 as, ok = s.addressSubscriptions[string(addrDesc)] 782 if ok { 783 for c, id := range as { 784 if c.IsAlive() { 785 c.out <- &websocketRes{ 786 ID: id, 787 Data: &data, 788 } 789 } 790 } 791 glog.Info("broadcasting new tx ", tx.Txid, " for addr ", addr[0], " to ", len(as), " channels") 792 } 793 } 794 } 795 } 796 797 func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float64) { 798 s.fiatRatesSubscriptionsLock.Lock() 799 defer s.fiatRatesSubscriptionsLock.Unlock() 800 as, ok := s.fiatRatesSubscriptions[currency] 801 if ok && len(as) > 0 { 802 data := struct { 803 Rates interface{} `json:"rates"` 804 }{ 805 Rates: rates, 806 } 807 // get the list of subscriptions again, this time keep the lock 808 as, ok = s.fiatRatesSubscriptions[currency] 809 if ok { 810 for c, id := range as { 811 if c.IsAlive() { 812 c.out <- &websocketRes{ 813 ID: id, 814 Data: &data, 815 } 816 } 817 } 818 glog.Info("broadcasting new rates for currency ", currency, " to ", len(as), " channels") 819 } 820 } 821 } 822 823 // OnNewFiatRatesTicker is a callback that broadcasts info about fiat rates affecting subscribed currency 824 func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) { 825 for currency, rate := range ticker.Rates { 826 s.broadcastTicker(currency, map[string]float64{currency: rate}) 827 } 828 s.broadcastTicker(allFiatRates, ticker.Rates) 829 } 830 831 func (s *WebsocketServer) getCurrentFiatRates(currencies []string) (interface{}, error) { 832 ret, err := s.api.GetCurrentFiatRates(currencies) 833 return ret, err 834 } 835 836 func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string) (interface{}, error) { 837 ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies) 838 return ret, err 839 } 840 841 func (s *WebsocketServer) getFiatRatesTickersList(timestamp int64) (interface{}, error) { 842 ret, err := s.api.GetFiatRatesTickersList(timestamp) 843 return ret, err 844 }