github.com/dawnbass68/maddcash@v0.0.0-20201001105353-c91c12cb36e5/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 "txs": 465 opt = api.AccountDetailsTxHistory 466 default: 467 opt = api.AccountDetailsBasic 468 } 469 var tokensToReturn api.TokensToReturn 470 switch req.Tokens { 471 case "used": 472 tokensToReturn = api.TokensToReturnUsed 473 case "nonzero": 474 tokensToReturn = api.TokensToReturnNonzeroBalance 475 default: 476 tokensToReturn = api.TokensToReturnDerived 477 } 478 filter := api.AddressFilter{ 479 FromHeight: uint32(req.FromHeight), 480 ToHeight: uint32(req.ToHeight), 481 Contract: req.ContractFilter, 482 Vout: api.AddressFilterVoutOff, 483 TokensToReturn: tokensToReturn, 484 } 485 if req.PageSize == 0 { 486 req.PageSize = txsOnPage 487 } 488 a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, req.Gap) 489 if err != nil { 490 return s.api.GetAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter) 491 } 492 return a, nil 493 } 494 495 func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error) { 496 utxo, err := s.api.GetXpubUtxo(descriptor, false, 0) 497 if err != nil { 498 return s.api.GetAddressUtxo(descriptor, false) 499 } 500 return utxo, nil 501 } 502 503 func (s *WebsocketServer) getTransaction(txid string) (interface{}, error) { 504 return s.api.GetTransaction(txid, false, false) 505 } 506 507 func (s *WebsocketServer) getTransactionSpecific(txid string) (interface{}, error) { 508 return s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid}) 509 } 510 511 func (s *WebsocketServer) getInfo() (interface{}, error) { 512 vi := common.GetVersionInfo() 513 height, hash, err := s.db.GetBestBlock() 514 if err != nil { 515 return nil, err 516 } 517 type info struct { 518 Name string `json:"name"` 519 Shortcut string `json:"shortcut"` 520 Decimals int `json:"decimals"` 521 Version string `json:"version"` 522 BestHeight int `json:"bestHeight"` 523 BestHash string `json:"bestHash"` 524 Block0Hash string `json:"block0Hash"` 525 Testnet bool `json:"testnet"` 526 } 527 return &info{ 528 Name: s.is.Coin, 529 Shortcut: s.is.CoinShortcut, 530 Decimals: s.chainParser.AmountDecimals(), 531 BestHeight: int(height), 532 BestHash: hash, 533 Version: vi.Version, 534 Block0Hash: s.block0hash, 535 Testnet: s.chain.IsTestnet(), 536 }, nil 537 } 538 539 func (s *WebsocketServer) getBlockHash(height int) (interface{}, error) { 540 h, err := s.db.GetBlockHash(uint32(height)) 541 if err != nil { 542 return nil, err 543 } 544 type hash struct { 545 Hash string `json:"hash"` 546 } 547 return &hash{ 548 Hash: h, 549 }, nil 550 } 551 552 func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (interface{}, error) { 553 type estimateFeeReq struct { 554 Blocks []int `json:"blocks"` 555 Specific map[string]interface{} `json:"specific"` 556 } 557 type estimateFeeRes struct { 558 FeePerTx string `json:"feePerTx,omitempty"` 559 FeePerUnit string `json:"feePerUnit,omitempty"` 560 FeeLimit string `json:"feeLimit,omitempty"` 561 } 562 var r estimateFeeReq 563 err := json.Unmarshal(params, &r) 564 if err != nil { 565 return nil, err 566 } 567 res := make([]estimateFeeRes, len(r.Blocks)) 568 if s.chainParser.GetChainType() == bchain.ChainEthereumType { 569 gas, err := s.chain.EthereumTypeEstimateGas(r.Specific) 570 if err != nil { 571 return nil, err 572 } 573 sg := strconv.FormatUint(gas, 10) 574 for i, b := range r.Blocks { 575 fee, err := s.chain.EstimateSmartFee(b, true) 576 if err != nil { 577 return nil, err 578 } 579 res[i].FeePerUnit = fee.String() 580 res[i].FeeLimit = sg 581 fee.Mul(&fee, new(big.Int).SetUint64(gas)) 582 res[i].FeePerTx = fee.String() 583 } 584 } else { 585 conservative := true 586 v, ok := r.Specific["conservative"] 587 if ok { 588 vc, ok := v.(bool) 589 if ok { 590 conservative = vc 591 } 592 } 593 txSize := 0 594 v, ok = r.Specific["txsize"] 595 if ok { 596 f, ok := v.(float64) 597 if ok { 598 txSize = int(f) 599 } 600 } 601 for i, b := range r.Blocks { 602 fee, err := s.chain.EstimateSmartFee(b, conservative) 603 if err != nil { 604 return nil, err 605 } 606 res[i].FeePerUnit = fee.String() 607 if txSize > 0 { 608 fee.Mul(&fee, big.NewInt(int64(txSize))) 609 fee.Add(&fee, big.NewInt(500)) 610 fee.Div(&fee, big.NewInt(1000)) 611 res[i].FeePerTx = fee.String() 612 } 613 } 614 } 615 return res, nil 616 } 617 618 func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction, err error) { 619 txid, err := s.chain.SendRawTransaction(tx) 620 if err != nil { 621 return res, err 622 } 623 res.Result = txid 624 return 625 } 626 627 type subscriptionResponse struct { 628 Subscribed bool `json:"subscribed"` 629 } 630 631 func (s *WebsocketServer) subscribeNewBlock(c *websocketChannel, req *websocketReq) (res interface{}, err error) { 632 s.newBlockSubscriptionsLock.Lock() 633 defer s.newBlockSubscriptionsLock.Unlock() 634 s.newBlockSubscriptions[c] = req.ID 635 return &subscriptionResponse{true}, nil 636 } 637 638 func (s *WebsocketServer) unsubscribeNewBlock(c *websocketChannel) (res interface{}, err error) { 639 s.newBlockSubscriptionsLock.Lock() 640 defer s.newBlockSubscriptionsLock.Unlock() 641 delete(s.newBlockSubscriptions, c) 642 return &subscriptionResponse{false}, nil 643 } 644 645 func (s *WebsocketServer) unmarshalAddresses(params []byte) ([]bchain.AddressDescriptor, error) { 646 r := struct { 647 Addresses []string `json:"addresses"` 648 }{} 649 err := json.Unmarshal(params, &r) 650 if err != nil { 651 return nil, err 652 } 653 rv := make([]bchain.AddressDescriptor, len(r.Addresses)) 654 for i, a := range r.Addresses { 655 ad, err := s.chainParser.GetAddrDescFromAddress(a) 656 if err != nil { 657 return nil, err 658 } 659 rv[i] = ad 660 } 661 return rv, nil 662 } 663 664 func (s *WebsocketServer) subscribeAddresses(c *websocketChannel, addrDesc []bchain.AddressDescriptor, req *websocketReq) (res interface{}, err error) { 665 // unsubscribe all previous subscriptions 666 s.unsubscribeAddresses(c) 667 s.addressSubscriptionsLock.Lock() 668 defer s.addressSubscriptionsLock.Unlock() 669 for i := range addrDesc { 670 ads := string(addrDesc[i]) 671 as, ok := s.addressSubscriptions[ads] 672 if !ok { 673 as = make(map[*websocketChannel]string) 674 s.addressSubscriptions[ads] = as 675 } 676 as[c] = req.ID 677 } 678 return &subscriptionResponse{true}, nil 679 } 680 681 // unsubscribeAddresses unsubscribes all address subscriptions by this channel 682 func (s *WebsocketServer) unsubscribeAddresses(c *websocketChannel) (res interface{}, err error) { 683 s.addressSubscriptionsLock.Lock() 684 defer s.addressSubscriptionsLock.Unlock() 685 for _, sa := range s.addressSubscriptions { 686 for sc := range sa { 687 if sc == c { 688 delete(sa, c) 689 } 690 } 691 } 692 return &subscriptionResponse{false}, nil 693 } 694 695 // subscribeFiatRates subscribes all FiatRates subscriptions by this channel 696 func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, currency string, req *websocketReq) (res interface{}, err error) { 697 // unsubscribe all previous subscriptions 698 s.unsubscribeFiatRates(c) 699 s.fiatRatesSubscriptionsLock.Lock() 700 defer s.fiatRatesSubscriptionsLock.Unlock() 701 702 if currency == "" { 703 currency = allFiatRates 704 } 705 as, ok := s.fiatRatesSubscriptions[currency] 706 if !ok { 707 as = make(map[*websocketChannel]string) 708 s.fiatRatesSubscriptions[currency] = as 709 } 710 as[c] = req.ID 711 return &subscriptionResponse{true}, nil 712 } 713 714 // unsubscribeFiatRates unsubscribes all FiatRates subscriptions by this channel 715 func (s *WebsocketServer) unsubscribeFiatRates(c *websocketChannel) (res interface{}, err error) { 716 s.fiatRatesSubscriptionsLock.Lock() 717 defer s.fiatRatesSubscriptionsLock.Unlock() 718 for _, sa := range s.fiatRatesSubscriptions { 719 for sc := range sa { 720 if sc == c { 721 delete(sa, c) 722 } 723 } 724 } 725 return &subscriptionResponse{false}, nil 726 } 727 728 // OnNewBlock is a callback that broadcasts info about new block to subscribed clients 729 func (s *WebsocketServer) OnNewBlock(hash string, height uint32) { 730 s.newBlockSubscriptionsLock.Lock() 731 defer s.newBlockSubscriptionsLock.Unlock() 732 data := struct { 733 Height uint32 `json:"height"` 734 Hash string `json:"hash"` 735 }{ 736 Height: height, 737 Hash: hash, 738 } 739 for c, id := range s.newBlockSubscriptions { 740 if c.IsAlive() { 741 c.out <- &websocketRes{ 742 ID: id, 743 Data: &data, 744 } 745 } 746 } 747 glog.Info("broadcasting new block ", height, " ", hash, " to ", len(s.newBlockSubscriptions), " channels") 748 } 749 750 // OnNewTxAddr is a callback that broadcasts info about a tx affecting subscribed address 751 func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDescriptor) { 752 // check if there is any subscription but release the lock immediately, GetTransactionFromBchainTx may take some time 753 s.addressSubscriptionsLock.Lock() 754 as, ok := s.addressSubscriptions[string(addrDesc)] 755 lenAs := len(as) 756 s.addressSubscriptionsLock.Unlock() 757 if ok && lenAs > 0 { 758 addr, _, err := s.chainParser.GetAddressesFromAddrDesc(addrDesc) 759 if err != nil { 760 glog.Error("GetAddressesFromAddrDesc error ", err, " for ", addrDesc) 761 return 762 } 763 if len(addr) == 1 { 764 atx, err := s.api.GetTransactionFromBchainTx(tx, 0, false, false) 765 if err != nil { 766 glog.Error("GetTransactionFromBchainTx error ", err, " for ", tx.Txid) 767 return 768 } 769 data := struct { 770 Address string `json:"address"` 771 Tx *api.Tx `json:"tx"` 772 }{ 773 Address: addr[0], 774 Tx: atx, 775 } 776 // get the list of subscriptions again, this time keep the lock 777 s.addressSubscriptionsLock.Lock() 778 defer s.addressSubscriptionsLock.Unlock() 779 as, ok = s.addressSubscriptions[string(addrDesc)] 780 if ok { 781 for c, id := range as { 782 if c.IsAlive() { 783 c.out <- &websocketRes{ 784 ID: id, 785 Data: &data, 786 } 787 } 788 } 789 glog.Info("broadcasting new tx ", tx.Txid, " for addr ", addr[0], " to ", len(as), " channels") 790 } 791 } 792 } 793 } 794 795 func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float64) { 796 s.fiatRatesSubscriptionsLock.Lock() 797 defer s.fiatRatesSubscriptionsLock.Unlock() 798 as, ok := s.fiatRatesSubscriptions[currency] 799 if ok && len(as) > 0 { 800 data := struct { 801 Rates interface{} `json:"rates"` 802 }{ 803 Rates: rates, 804 } 805 // get the list of subscriptions again, this time keep the lock 806 as, ok = s.fiatRatesSubscriptions[currency] 807 if ok { 808 for c, id := range as { 809 if c.IsAlive() { 810 c.out <- &websocketRes{ 811 ID: id, 812 Data: &data, 813 } 814 } 815 } 816 glog.Info("broadcasting new rates for currency ", currency, " to ", len(as), " channels") 817 } 818 } 819 } 820 821 // OnNewFiatRatesTicker is a callback that broadcasts info about fiat rates affecting subscribed currency 822 func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) { 823 for currency, rate := range ticker.Rates { 824 s.broadcastTicker(currency, map[string]float64{currency: rate}) 825 } 826 s.broadcastTicker(allFiatRates, ticker.Rates) 827 } 828 829 func (s *WebsocketServer) getCurrentFiatRates(currencies []string) (interface{}, error) { 830 ret, err := s.api.GetCurrentFiatRates(currencies) 831 return ret, err 832 } 833 834 func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string) (interface{}, error) { 835 ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies) 836 return ret, err 837 } 838 839 func (s *WebsocketServer) getFiatRatesTickersList(timestamp int64) (interface{}, error) { 840 ret, err := s.api.GetFiatRatesTickersList(timestamp) 841 return ret, err 842 }