github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/server/websocket.go (about) 1 package server 2 3 import ( 4 "encoding/json" 5 "math/big" 6 "net/http" 7 "runtime/debug" 8 "strconv" 9 "strings" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "github.com/cryptohub-digital/blockbook-fork/api" 15 "github.com/cryptohub-digital/blockbook-fork/bchain" 16 "github.com/cryptohub-digital/blockbook-fork/common" 17 "github.com/cryptohub-digital/blockbook-fork/db" 18 "github.com/cryptohub-digital/blockbook-fork/fiat" 19 "github.com/golang/glog" 20 "github.com/gorilla/websocket" 21 "github.com/juju/errors" 22 ) 23 24 const upgradeFailed = "Upgrade failed: " 25 const outChannelSize = 500 26 const defaultTimeout = 60 * time.Second 27 28 // allRates is a special "currency" parameter that means all available currencies 29 const allFiatRates = "!ALL!" 30 31 var ( 32 // ErrorMethodNotAllowed is returned when client tries to upgrade method other than GET 33 ErrorMethodNotAllowed = errors.New("Method not allowed") 34 35 connectionCounter uint64 36 ) 37 38 type websocketChannel struct { 39 id uint64 40 conn *websocket.Conn 41 out chan *WsRes 42 ip string 43 requestHeader http.Header 44 alive bool 45 aliveLock sync.Mutex 46 addrDescs []string // subscribed address descriptors as strings 47 } 48 49 // WebsocketServer is a handle to websocket server 50 type WebsocketServer struct { 51 upgrader *websocket.Upgrader 52 db *db.RocksDB 53 txCache *db.TxCache 54 chain bchain.BlockChain 55 chainParser bchain.BlockChainParser 56 mempool bchain.Mempool 57 metrics *common.Metrics 58 is *common.InternalState 59 api *api.Worker 60 block0hash string 61 newBlockSubscriptions map[*websocketChannel]string 62 newBlockSubscriptionsLock sync.Mutex 63 newTransactionEnabled bool 64 newTransactionSubscriptions map[*websocketChannel]string 65 newTransactionSubscriptionsLock sync.Mutex 66 addressSubscriptions map[string]map[*websocketChannel]string 67 addressSubscriptionsLock sync.Mutex 68 fiatRatesSubscriptions map[string]map[*websocketChannel]string 69 fiatRatesTokenSubscriptions map[*websocketChannel][]string 70 fiatRatesSubscriptionsLock sync.Mutex 71 } 72 73 // NewWebsocketServer creates new websocket interface to blockbook and returns its handle 74 func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*WebsocketServer, error) { 75 api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) 76 if err != nil { 77 return nil, err 78 } 79 b0, err := db.GetBlockHash(0) 80 if err != nil { 81 return nil, err 82 } 83 s := &WebsocketServer{ 84 upgrader: &websocket.Upgrader{ 85 ReadBufferSize: 1024 * 32, 86 WriteBufferSize: 1024 * 32, 87 CheckOrigin: checkOrigin, 88 }, 89 db: db, 90 txCache: txCache, 91 chain: chain, 92 chainParser: chain.GetChainParser(), 93 mempool: mempool, 94 metrics: metrics, 95 is: is, 96 api: api, 97 block0hash: b0, 98 newBlockSubscriptions: make(map[*websocketChannel]string), 99 newTransactionEnabled: is.EnableSubNewTx, 100 newTransactionSubscriptions: make(map[*websocketChannel]string), 101 addressSubscriptions: make(map[string]map[*websocketChannel]string), 102 fiatRatesSubscriptions: make(map[string]map[*websocketChannel]string), 103 fiatRatesTokenSubscriptions: make(map[*websocketChannel][]string), 104 } 105 return s, nil 106 } 107 108 // allow all origins 109 func checkOrigin(r *http.Request) bool { 110 return true 111 } 112 113 func getIP(r *http.Request) string { 114 ip := r.Header.Get("X-Real-Ip") 115 if ip != "" { 116 return ip 117 } 118 return r.RemoteAddr 119 } 120 121 // ServeHTTP sets up handler of websocket channel 122 func (s *WebsocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 123 if r.Method != "GET" { 124 http.Error(w, upgradeFailed+ErrorMethodNotAllowed.Error(), 503) 125 return 126 } 127 conn, err := s.upgrader.Upgrade(w, r, nil) 128 if err != nil { 129 http.Error(w, upgradeFailed+err.Error(), 503) 130 return 131 } 132 c := &websocketChannel{ 133 id: atomic.AddUint64(&connectionCounter, 1), 134 conn: conn, 135 out: make(chan *WsRes, outChannelSize), 136 ip: getIP(r), 137 requestHeader: r.Header, 138 alive: true, 139 } 140 go s.inputLoop(c) 141 go s.outputLoop(c) 142 s.onConnect(c) 143 } 144 145 // GetHandler returns http handler 146 func (s *WebsocketServer) GetHandler() http.Handler { 147 return s 148 } 149 150 func (s *WebsocketServer) closeChannel(c *websocketChannel) { 151 if c.CloseOut() { 152 c.conn.Close() 153 s.onDisconnect(c) 154 } 155 } 156 157 func (c *websocketChannel) CloseOut() bool { 158 c.aliveLock.Lock() 159 defer c.aliveLock.Unlock() 160 if c.alive { 161 c.alive = false 162 //clean out 163 close(c.out) 164 for len(c.out) > 0 { 165 <-c.out 166 } 167 return true 168 } 169 return false 170 } 171 172 func (c *websocketChannel) DataOut(data *WsRes) { 173 c.aliveLock.Lock() 174 defer c.aliveLock.Unlock() 175 if c.alive { 176 if len(c.out) < outChannelSize-1 { 177 c.out <- data 178 } else { 179 glog.Warning("Channel ", c.id, " overflow, closing") 180 // close the connection but do not call CloseOut - would call duplicate c.aliveLock.Lock 181 // CloseOut will be called because the closed connection will cause break in the inputLoop 182 c.conn.Close() 183 } 184 } 185 } 186 187 func (s *WebsocketServer) inputLoop(c *websocketChannel) { 188 defer func() { 189 if r := recover(); r != nil { 190 glog.Error("recovered from panic: ", r, ", ", c.id) 191 debug.PrintStack() 192 s.closeChannel(c) 193 } 194 }() 195 for { 196 t, d, err := c.conn.ReadMessage() 197 if err != nil { 198 s.closeChannel(c) 199 return 200 } 201 switch t { 202 case websocket.TextMessage: 203 var req WsReq 204 err := json.Unmarshal(d, &req) 205 if err != nil { 206 glog.Error("Error parsing message from ", c.id, ", ", string(d), ", ", err) 207 s.closeChannel(c) 208 return 209 } 210 go s.onRequest(c, &req) 211 case websocket.BinaryMessage: 212 glog.Error("Binary message received from ", c.id, ", ", c.ip) 213 s.closeChannel(c) 214 return 215 case websocket.PingMessage: 216 c.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(defaultTimeout)) 217 case websocket.CloseMessage: 218 s.closeChannel(c) 219 return 220 case websocket.PongMessage: 221 // do nothing 222 } 223 } 224 } 225 226 func (s *WebsocketServer) outputLoop(c *websocketChannel) { 227 defer func() { 228 if r := recover(); r != nil { 229 glog.Error("recovered from panic: ", r, ", ", c.id) 230 s.closeChannel(c) 231 } 232 }() 233 for m := range c.out { 234 err := c.conn.WriteJSON(m) 235 if err != nil { 236 glog.Error("Error sending message to ", c.id, ", ", err) 237 s.closeChannel(c) 238 return 239 } 240 } 241 } 242 243 func (s *WebsocketServer) onConnect(c *websocketChannel) { 244 glog.Info("Client connected ", c.id, ", ", c.ip) 245 s.metrics.WebsocketClients.Inc() 246 } 247 248 func (s *WebsocketServer) onDisconnect(c *websocketChannel) { 249 s.unsubscribeNewBlock(c) 250 s.unsubscribeNewTransaction(c) 251 s.unsubscribeAddresses(c) 252 s.unsubscribeFiatRates(c) 253 glog.Info("Client disconnected ", c.id, ", ", c.ip) 254 s.metrics.WebsocketClients.Dec() 255 } 256 257 var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *WsReq) (interface{}, error){ 258 "getAccountInfo": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 259 r, err := unmarshalGetAccountInfoRequest(req.Params) 260 if err == nil { 261 rv, err = s.getAccountInfo(r) 262 } 263 return 264 }, 265 "getInfo": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 266 return s.getInfo() 267 }, 268 "getBlockHash": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 269 r := WsBlockHashReq{} 270 err = json.Unmarshal(req.Params, &r) 271 if err == nil { 272 rv, err = s.getBlockHash(r.Height) 273 } 274 return 275 }, 276 "getBlock": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 277 if !s.is.ExtendedIndex { 278 return nil, errors.New("Not supported") 279 } 280 r := WsBlockReq{} 281 err = json.Unmarshal(req.Params, &r) 282 if r.PageSize == 0 { 283 r.PageSize = 1000000 284 } 285 if err == nil { 286 rv, err = s.getBlock(r.Id, r.Page, r.PageSize) 287 } 288 return 289 }, 290 "getAccountUtxo": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 291 r := WsAccountUtxoReq{} 292 err = json.Unmarshal(req.Params, &r) 293 if err == nil { 294 rv, err = s.getAccountUtxo(r.Descriptor) 295 } 296 return 297 }, 298 "getBalanceHistory": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 299 r := WsBalanceHistoryReq{} 300 err = json.Unmarshal(req.Params, &r) 301 if err == nil { 302 if r.From <= 0 { 303 r.From = 0 304 } 305 if r.To <= 0 { 306 r.To = 0 307 } 308 if r.GroupBy <= 0 { 309 r.GroupBy = 3600 310 } 311 rv, err = s.api.GetXpubBalanceHistory(r.Descriptor, r.From, r.To, r.Currencies, r.Gap, r.GroupBy) 312 if err != nil { 313 rv, err = s.api.GetBalanceHistory(r.Descriptor, r.From, r.To, r.Currencies, r.GroupBy) 314 } 315 } 316 return 317 }, 318 "getTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 319 r := WsTransactionReq{} 320 err = json.Unmarshal(req.Params, &r) 321 if err == nil { 322 rv, err = s.getTransaction(r.Txid) 323 } 324 return 325 }, 326 "getTransactionSpecific": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 327 r := WsTransactionSpecificReq{} 328 err = json.Unmarshal(req.Params, &r) 329 if err == nil { 330 rv, err = s.getTransactionSpecific(r.Txid) 331 } 332 return 333 }, 334 "estimateFee": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 335 return s.estimateFee(c, req.Params) 336 }, 337 "sendTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 338 r := WsSendTransactionReq{} 339 err = json.Unmarshal(req.Params, &r) 340 if err == nil { 341 rv, err = s.sendTransaction(r.Hex) 342 } 343 return 344 }, 345 "getMempoolFilters": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 346 r := WsMempoolFiltersReq{} 347 err = json.Unmarshal(req.Params, &r) 348 if err == nil { 349 rv, err = s.getMempoolFilters(&r) 350 } 351 return 352 }, 353 "subscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 354 return s.subscribeNewBlock(c, req) 355 }, 356 "unsubscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 357 return s.unsubscribeNewBlock(c) 358 }, 359 "subscribeNewTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 360 return s.subscribeNewTransaction(c, req) 361 }, 362 "unsubscribeNewTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 363 return s.unsubscribeNewTransaction(c) 364 }, 365 "subscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 366 ad, err := s.unmarshalAddresses(req.Params) 367 if err == nil { 368 rv, err = s.subscribeAddresses(c, ad, req) 369 } 370 return 371 }, 372 "unsubscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 373 return s.unsubscribeAddresses(c) 374 }, 375 "subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 376 var r WsSubscribeFiatRatesReq 377 err = json.Unmarshal(req.Params, &r) 378 if err != nil { 379 return nil, err 380 } 381 r.Currency = strings.ToLower(r.Currency) 382 for i := range r.Tokens { 383 r.Tokens[i] = strings.ToLower(r.Tokens[i]) 384 } 385 return s.subscribeFiatRates(c, &r, req) 386 }, 387 "unsubscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 388 return s.unsubscribeFiatRates(c) 389 }, 390 "ping": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 391 r := struct{}{} 392 return r, nil 393 }, 394 "getCurrentFiatRates": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 395 r := WsCurrentFiatRatesReq{} 396 err = json.Unmarshal(req.Params, &r) 397 if err == nil { 398 rv, err = s.getCurrentFiatRates(r.Currencies, r.Token) 399 } 400 return 401 }, 402 "getFiatRatesForTimestamps": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 403 r := WsFiatRatesForTimestampsReq{} 404 err = json.Unmarshal(req.Params, &r) 405 if err == nil { 406 rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies, r.Token) 407 } 408 return 409 }, 410 "getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) { 411 r := WsFiatRatesTickersListReq{} 412 err = json.Unmarshal(req.Params, &r) 413 if err == nil { 414 rv, err = s.getAvailableVsCurrencies(r.Timestamp, r.Token) 415 } 416 return 417 }, 418 } 419 420 func (s *WebsocketServer) onRequest(c *websocketChannel, req *WsReq) { 421 var err error 422 var data interface{} 423 defer func() { 424 if r := recover(); r != nil { 425 glog.Error("Client ", c.id, ", onRequest ", req.Method, " recovered from panic: ", r) 426 debug.PrintStack() 427 e := resultError{} 428 e.Error.Message = "Internal error" 429 data = e 430 } 431 // nil data means no response 432 if data != nil { 433 c.DataOut(&WsRes{ 434 ID: req.ID, 435 Data: data, 436 }) 437 } 438 s.metrics.WebsocketPendingRequests.With((common.Labels{"method": req.Method})).Dec() 439 }() 440 t := time.Now() 441 s.metrics.WebsocketPendingRequests.With((common.Labels{"method": req.Method})).Inc() 442 defer s.metrics.WebsocketReqDuration.With(common.Labels{"method": req.Method}).Observe(float64(time.Since(t)) / 1e3) // in microseconds 443 f, ok := requestHandlers[req.Method] 444 if ok { 445 data, err = f(s, c, req) 446 if err == nil { 447 glog.V(1).Info("Client ", c.id, " onRequest ", req.Method, " success") 448 s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": "success"}).Inc() 449 } else { 450 if apiErr, ok := err.(*api.APIError); !ok || !apiErr.Public { 451 glog.Error("Client ", c.id, " onMessage ", req.Method, ": ", errors.ErrorStack(err), ", data ", string(req.Params)) 452 } 453 s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": "failure"}).Inc() 454 e := resultError{} 455 e.Error.Message = err.Error() 456 data = e 457 } 458 } else { 459 glog.V(1).Info("Client ", c.id, " onMessage ", req.Method, ": unknown method, data ", string(req.Params)) 460 } 461 } 462 463 func unmarshalGetAccountInfoRequest(params []byte) (*WsAccountInfoReq, error) { 464 var r WsAccountInfoReq 465 err := json.Unmarshal(params, &r) 466 if err != nil { 467 return nil, err 468 } 469 return &r, nil 470 } 471 472 func (s *WebsocketServer) getAccountInfo(req *WsAccountInfoReq) (res *api.Address, err error) { 473 var opt api.AccountDetails 474 switch req.Details { 475 case "tokens": 476 opt = api.AccountDetailsTokens 477 case "tokenBalances": 478 opt = api.AccountDetailsTokenBalances 479 case "txids": 480 opt = api.AccountDetailsTxidHistory 481 case "txslight": 482 opt = api.AccountDetailsTxHistoryLight 483 case "txs": 484 opt = api.AccountDetailsTxHistory 485 default: 486 opt = api.AccountDetailsBasic 487 } 488 var tokensToReturn api.TokensToReturn 489 switch req.Tokens { 490 case "used": 491 tokensToReturn = api.TokensToReturnUsed 492 case "nonzero": 493 tokensToReturn = api.TokensToReturnNonzeroBalance 494 default: 495 tokensToReturn = api.TokensToReturnDerived 496 } 497 filter := api.AddressFilter{ 498 FromHeight: uint32(req.FromHeight), 499 ToHeight: uint32(req.ToHeight), 500 Contract: req.ContractFilter, 501 Vout: api.AddressFilterVoutOff, 502 TokensToReturn: tokensToReturn, 503 } 504 if req.PageSize == 0 { 505 req.PageSize = txsOnPage 506 } 507 a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, req.Gap, strings.ToLower(req.SecondaryCurrency)) 508 if err != nil { 509 return s.api.GetAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, strings.ToLower(req.SecondaryCurrency)) 510 } 511 return a, nil 512 } 513 514 func (s *WebsocketServer) getAccountUtxo(descriptor string) (api.Utxos, error) { 515 utxo, err := s.api.GetXpubUtxo(descriptor, false, 0) 516 if err != nil { 517 return s.api.GetAddressUtxo(descriptor, false) 518 } 519 return utxo, nil 520 } 521 522 func (s *WebsocketServer) getTransaction(txid string) (*api.Tx, error) { 523 return s.api.GetTransaction(txid, false, false) 524 } 525 526 func (s *WebsocketServer) getTransactionSpecific(txid string) (interface{}, error) { 527 return s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid}) 528 } 529 530 func (s *WebsocketServer) getInfo() (*WsInfoRes, error) { 531 vi := common.GetVersionInfo() 532 bi := s.is.GetBackendInfo() 533 height, hash, err := s.db.GetBestBlock() 534 if err != nil { 535 return nil, err 536 } 537 return &WsInfoRes{ 538 Name: s.is.Coin, 539 Shortcut: s.is.CoinShortcut, 540 Decimals: s.chainParser.AmountDecimals(), 541 BestHeight: int(height), 542 BestHash: hash, 543 Version: vi.Version, 544 Block0Hash: s.block0hash, 545 Testnet: s.chain.IsTestnet(), 546 Backend: WsBackendInfo{ 547 Version: bi.Version, 548 Subversion: bi.Subversion, 549 ConsensusVersion: bi.ConsensusVersion, 550 Consensus: bi.Consensus, 551 }, 552 }, nil 553 } 554 555 func (s *WebsocketServer) getBlockHash(height int) (*WsBlockHashRes, error) { 556 h, err := s.db.GetBlockHash(uint32(height)) 557 if err != nil { 558 return nil, err 559 } 560 return &WsBlockHashRes{ 561 Hash: h, 562 }, nil 563 } 564 565 func (s *WebsocketServer) getBlock(id string, page, pageSize int) (interface{}, error) { 566 block, err := s.api.GetBlock(id, page, pageSize) 567 if err != nil { 568 return nil, err 569 } 570 return block, nil 571 } 572 573 func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (interface{}, error) { 574 var r WsEstimateFeeReq 575 err := json.Unmarshal(params, &r) 576 if err != nil { 577 return nil, err 578 } 579 res := make([]WsEstimateFeeRes, len(r.Blocks)) 580 if s.chainParser.GetChainType() == bchain.ChainEthereumType { 581 gas, err := s.chain.EthereumTypeEstimateGas(r.Specific) 582 if err != nil { 583 return nil, err 584 } 585 sg := strconv.FormatUint(gas, 10) 586 b := 1 587 if len(r.Blocks) > 0 { 588 b = r.Blocks[0] 589 } 590 fee, err := s.api.EstimateFee(b, true) 591 if err != nil { 592 return nil, err 593 } 594 for i := range r.Blocks { 595 res[i].FeePerUnit = fee.String() 596 res[i].FeeLimit = sg 597 fee.Mul(&fee, new(big.Int).SetUint64(gas)) 598 res[i].FeePerTx = fee.String() 599 } 600 } else if s.chainParser.GetChainType() == bchain.ChainCoreCoinType { 601 energy, err := s.chain.CoreCoinTypeEstimateEnergy(r.Specific) 602 if err != nil { 603 return nil, err 604 } 605 sg := strconv.FormatUint(energy, 10) 606 b := 1 607 if len(r.Blocks) > 0 { 608 b = r.Blocks[0] 609 } 610 fee, err := s.api.EstimateFee(b, true) 611 if err != nil { 612 return nil, err 613 } 614 for i := range r.Blocks { 615 res[i].FeePerUnit = fee.String() 616 res[i].FeeLimit = sg 617 fee.Mul(&fee, new(big.Int).SetUint64(energy)) 618 res[i].FeePerTx = fee.String() 619 } 620 } else { 621 conservative := true 622 v, ok := r.Specific["conservative"] 623 if ok { 624 vc, ok := v.(bool) 625 if ok { 626 conservative = vc 627 } 628 } 629 txSize := 0 630 v, ok = r.Specific["txsize"] 631 if ok { 632 f, ok := v.(float64) 633 if ok { 634 txSize = int(f) 635 } 636 } 637 for i, b := range r.Blocks { 638 fee, err := s.api.EstimateFee(b, conservative) 639 if err != nil { 640 return nil, err 641 } 642 res[i].FeePerUnit = fee.String() 643 if txSize > 0 { 644 fee.Mul(&fee, big.NewInt(int64(txSize))) 645 fee.Add(&fee, big.NewInt(500)) 646 fee.Div(&fee, big.NewInt(1000)) 647 res[i].FeePerTx = fee.String() 648 } 649 } 650 } 651 return res, nil 652 } 653 654 func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction, err error) { 655 txid, err := s.chain.SendRawTransaction(tx) 656 if err != nil { 657 return res, err 658 } 659 res.Result = txid 660 return 661 } 662 663 func (s *WebsocketServer) getMempoolFilters(r *WsMempoolFiltersReq) (res bchain.MempoolTxidFilterEntries, err error) { 664 res, err = s.mempool.GetTxidFilterEntries(r.ScriptType, r.FromTimestamp) 665 return 666 } 667 668 type subscriptionResponse struct { 669 Subscribed bool `json:"subscribed"` 670 } 671 type subscriptionResponseMessage struct { 672 Subscribed bool `json:"subscribed"` 673 Message string `json:"message"` 674 } 675 676 func (s *WebsocketServer) subscribeNewBlock(c *websocketChannel, req *WsReq) (res interface{}, err error) { 677 s.newBlockSubscriptionsLock.Lock() 678 defer s.newBlockSubscriptionsLock.Unlock() 679 s.newBlockSubscriptions[c] = req.ID 680 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeNewBlock"})).Set(float64(len(s.newBlockSubscriptions))) 681 return &subscriptionResponse{true}, nil 682 } 683 684 func (s *WebsocketServer) unsubscribeNewBlock(c *websocketChannel) (res interface{}, err error) { 685 s.newBlockSubscriptionsLock.Lock() 686 defer s.newBlockSubscriptionsLock.Unlock() 687 delete(s.newBlockSubscriptions, c) 688 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeNewBlock"})).Set(float64(len(s.newBlockSubscriptions))) 689 return &subscriptionResponse{false}, nil 690 } 691 692 func (s *WebsocketServer) subscribeNewTransaction(c *websocketChannel, req *WsReq) (res interface{}, err error) { 693 s.newTransactionSubscriptionsLock.Lock() 694 defer s.newTransactionSubscriptionsLock.Unlock() 695 if !s.newTransactionEnabled { 696 return &subscriptionResponseMessage{false, "subscribeNewTransaction not enabled, use -enablesubnewtx flag to enable."}, nil 697 } 698 s.newTransactionSubscriptions[c] = req.ID 699 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeNewTransaction"})).Set(float64(len(s.newTransactionSubscriptions))) 700 return &subscriptionResponse{true}, nil 701 } 702 703 func (s *WebsocketServer) unsubscribeNewTransaction(c *websocketChannel) (res interface{}, err error) { 704 s.newTransactionSubscriptionsLock.Lock() 705 defer s.newTransactionSubscriptionsLock.Unlock() 706 if !s.newTransactionEnabled { 707 return &subscriptionResponseMessage{false, "unsubscribeNewTransaction not enabled, use -enablesubnewtx flag to enable."}, nil 708 } 709 delete(s.newTransactionSubscriptions, c) 710 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeNewTransaction"})).Set(float64(len(s.newTransactionSubscriptions))) 711 return &subscriptionResponse{false}, nil 712 } 713 714 func (s *WebsocketServer) unmarshalAddresses(params []byte) ([]string, error) { 715 r := WsSubscribeAddressesReq{} 716 err := json.Unmarshal(params, &r) 717 if err != nil { 718 return nil, err 719 } 720 rv := make([]string, len(r.Addresses)) 721 for i, a := range r.Addresses { 722 ad, err := s.chainParser.GetAddrDescFromAddress(a) 723 if err != nil { 724 return nil, err 725 } 726 rv[i] = string(ad) 727 } 728 return rv, nil 729 } 730 731 // unsubscribe addresses without addressSubscriptionsLock - can be called only from subscribeAddresses and unsubscribeAddresses 732 func (s *WebsocketServer) doUnsubscribeAddresses(c *websocketChannel) { 733 for _, ads := range c.addrDescs { 734 sa, e := s.addressSubscriptions[ads] 735 if e { 736 for sc := range sa { 737 if sc == c { 738 delete(sa, c) 739 } 740 } 741 if len(sa) == 0 { 742 delete(s.addressSubscriptions, ads) 743 } 744 } 745 } 746 c.addrDescs = nil 747 } 748 749 func (s *WebsocketServer) subscribeAddresses(c *websocketChannel, addrDesc []string, req *WsReq) (res interface{}, err error) { 750 s.addressSubscriptionsLock.Lock() 751 defer s.addressSubscriptionsLock.Unlock() 752 // unsubscribe all previous subscriptions 753 s.doUnsubscribeAddresses(c) 754 for _, ads := range addrDesc { 755 as, ok := s.addressSubscriptions[ads] 756 if !ok { 757 as = make(map[*websocketChannel]string) 758 s.addressSubscriptions[ads] = as 759 } 760 as[c] = req.ID 761 } 762 c.addrDescs = addrDesc 763 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeAddresses"})).Set(float64(len(s.addressSubscriptions))) 764 return &subscriptionResponse{true}, nil 765 } 766 767 // unsubscribeAddresses unsubscribes all address subscriptions by this channel 768 func (s *WebsocketServer) unsubscribeAddresses(c *websocketChannel) (res interface{}, err error) { 769 s.addressSubscriptionsLock.Lock() 770 defer s.addressSubscriptionsLock.Unlock() 771 s.doUnsubscribeAddresses(c) 772 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeAddresses"})).Set(float64(len(s.addressSubscriptions))) 773 return &subscriptionResponse{false}, nil 774 } 775 776 // unsubscribe fiat rates without fiatRatesSubscriptionsLock - can be called only from subscribeFiatRates and unsubscribeFiatRates 777 func (s *WebsocketServer) doUnsubscribeFiatRates(c *websocketChannel) { 778 for fr, sa := range s.fiatRatesSubscriptions { 779 for sc := range sa { 780 if sc == c { 781 delete(sa, c) 782 } 783 } 784 if len(sa) == 0 { 785 delete(s.fiatRatesSubscriptions, fr) 786 } 787 } 788 delete(s.fiatRatesTokenSubscriptions, c) 789 } 790 791 // subscribeFiatRates subscribes all FiatRates subscriptions by this channel 792 func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, d *WsSubscribeFiatRatesReq, req *WsReq) (res interface{}, err error) { 793 s.fiatRatesSubscriptionsLock.Lock() 794 defer s.fiatRatesSubscriptionsLock.Unlock() 795 // unsubscribe all previous subscriptions 796 s.doUnsubscribeFiatRates(c) 797 currency := d.Currency 798 if currency == "" { 799 currency = allFiatRates 800 } else { 801 currency = strings.ToLower(currency) 802 } 803 as, ok := s.fiatRatesSubscriptions[currency] 804 if !ok { 805 as = make(map[*websocketChannel]string) 806 s.fiatRatesSubscriptions[currency] = as 807 } 808 as[c] = req.ID 809 if len(d.Tokens) != 0 { 810 s.fiatRatesTokenSubscriptions[c] = d.Tokens 811 } 812 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeFiatRates"})).Set(float64(len(s.fiatRatesSubscriptions))) 813 return &subscriptionResponse{true}, nil 814 } 815 816 // unsubscribeFiatRates unsubscribes all FiatRates subscriptions by this channel 817 func (s *WebsocketServer) unsubscribeFiatRates(c *websocketChannel) (res interface{}, err error) { 818 s.fiatRatesSubscriptionsLock.Lock() 819 defer s.fiatRatesSubscriptionsLock.Unlock() 820 s.doUnsubscribeFiatRates(c) 821 s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeFiatRates"})).Set(float64(len(s.fiatRatesSubscriptions))) 822 return &subscriptionResponse{false}, nil 823 } 824 825 func (s *WebsocketServer) onNewBlockAsync(hash string, height uint32) { 826 s.newBlockSubscriptionsLock.Lock() 827 defer s.newBlockSubscriptionsLock.Unlock() 828 data := struct { 829 Height uint32 `json:"height"` 830 Hash string `json:"hash"` 831 }{ 832 Height: height, 833 Hash: hash, 834 } 835 for c, id := range s.newBlockSubscriptions { 836 c.DataOut(&WsRes{ 837 ID: id, 838 Data: &data, 839 }) 840 } 841 glog.Info("broadcasting new block ", height, " ", hash, " to ", len(s.newBlockSubscriptions), " channels") 842 } 843 844 // OnNewBlock is a callback that broadcasts info about new block to subscribed clients 845 func (s *WebsocketServer) OnNewBlock(hash string, height uint32) { 846 go s.onNewBlockAsync(hash, height) 847 } 848 849 func (s *WebsocketServer) sendOnNewTx(tx *api.Tx) { 850 s.newTransactionSubscriptionsLock.Lock() 851 defer s.newTransactionSubscriptionsLock.Unlock() 852 for c, id := range s.newTransactionSubscriptions { 853 c.DataOut(&WsRes{ 854 ID: id, 855 Data: &tx, 856 }) 857 } 858 glog.Info("broadcasting new tx ", tx.Txid, " to ", len(s.newTransactionSubscriptions), " channels") 859 } 860 861 func (s *WebsocketServer) sendOnNewTxAddr(stringAddressDescriptor string, tx *api.Tx) { 862 addrDesc := bchain.AddressDescriptor(stringAddressDescriptor) 863 addr, _, err := s.chainParser.GetAddressesFromAddrDesc(addrDesc) 864 if err != nil { 865 glog.Error("GetAddressesFromAddrDesc error ", err, " for ", addrDesc) 866 return 867 } 868 if len(addr) == 1 { 869 data := struct { 870 Address string `json:"address"` 871 Tx *api.Tx `json:"tx"` 872 }{ 873 Address: addr[0], 874 Tx: tx, 875 } 876 s.addressSubscriptionsLock.Lock() 877 defer s.addressSubscriptionsLock.Unlock() 878 as, ok := s.addressSubscriptions[stringAddressDescriptor] 879 if ok { 880 for c, id := range as { 881 c.DataOut(&WsRes{ 882 ID: id, 883 Data: &data, 884 }) 885 } 886 glog.Info("broadcasting new tx ", tx.Txid, ", addr ", addr[0], " to ", len(as), " channels") 887 } 888 } 889 } 890 891 func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string]struct{} { 892 // check if there is any subscription in inputs, outputs and token transfers 893 s.addressSubscriptionsLock.Lock() 894 defer s.addressSubscriptionsLock.Unlock() 895 subscribed := make(map[string]struct{}) 896 for i := range tx.Vin { 897 sad := string(tx.Vin[i].AddrDesc) 898 if len(sad) > 0 { 899 as, ok := s.addressSubscriptions[sad] 900 if ok && len(as) > 0 { 901 subscribed[sad] = struct{}{} 902 } 903 } 904 } 905 for i := range tx.Vout { 906 addrDesc, err := s.chainParser.GetAddrDescFromVout(&tx.Vout[i]) 907 if err == nil && len(addrDesc) > 0 { 908 sad := string(addrDesc) 909 as, ok := s.addressSubscriptions[sad] 910 if ok && len(as) > 0 { 911 subscribed[sad] = struct{}{} 912 } 913 } 914 } 915 for i := range tx.TokenTransfers { 916 addrDesc, err := s.chainParser.GetAddrDescFromAddress(tx.TokenTransfers[i].From) 917 if err == nil && len(addrDesc) > 0 { 918 sad := string(addrDesc) 919 as, ok := s.addressSubscriptions[sad] 920 if ok && len(as) > 0 { 921 subscribed[sad] = struct{}{} 922 } 923 } 924 addrDesc, err = s.chainParser.GetAddrDescFromAddress(tx.TokenTransfers[i].To) 925 if err == nil && len(addrDesc) > 0 { 926 sad := string(addrDesc) 927 as, ok := s.addressSubscriptions[sad] 928 if ok && len(as) > 0 { 929 subscribed[sad] = struct{}{} 930 } 931 } 932 } 933 return subscribed 934 } 935 936 func (s *WebsocketServer) onNewTxAsync(tx *bchain.MempoolTx, subscribed map[string]struct{}) { 937 atx, err := s.api.GetTransactionFromMempoolTx(tx) 938 if err != nil { 939 glog.Error("GetTransactionFromMempoolTx error ", err, " for ", tx.Txid) 940 return 941 } 942 s.sendOnNewTx(atx) 943 for stringAddressDescriptor := range subscribed { 944 s.sendOnNewTxAddr(stringAddressDescriptor, atx) 945 } 946 } 947 948 // OnNewTx is a callback that broadcasts info about a tx affecting subscribed address 949 func (s *WebsocketServer) OnNewTx(tx *bchain.MempoolTx) { 950 subscribed := s.getNewTxSubscriptions(tx) 951 if len(s.newTransactionSubscriptions) > 0 || len(subscribed) > 0 { 952 go s.onNewTxAsync(tx, subscribed) 953 } 954 } 955 956 func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float32, ticker *common.CurrencyRatesTicker) { 957 as, ok := s.fiatRatesSubscriptions[currency] 958 if ok && len(as) > 0 { 959 data := struct { 960 Rates interface{} `json:"rates"` 961 }{ 962 Rates: rates, 963 } 964 for c, id := range as { 965 var tokens []string 966 if ticker != nil { 967 tokens = s.fiatRatesTokenSubscriptions[c] 968 } 969 if len(tokens) > 0 { 970 dataWithTokens := struct { 971 Rates interface{} `json:"rates"` 972 TokenRates map[string]float32 `json:"tokenRates,omitempty"` 973 }{ 974 Rates: rates, 975 TokenRates: map[string]float32{}, 976 } 977 for _, token := range tokens { 978 rate := ticker.TokenRateInCurrency(token, currency) 979 if rate > 0 { 980 dataWithTokens.TokenRates[token] = rate 981 } 982 } 983 c.DataOut(&WsRes{ 984 ID: id, 985 Data: &dataWithTokens, 986 }) 987 } else { 988 c.DataOut(&WsRes{ 989 ID: id, 990 Data: &data, 991 }) 992 } 993 } 994 glog.Info("broadcasting new rates for currency ", currency, " to ", len(as), " channels") 995 } 996 } 997 998 // OnNewFiatRatesTicker is a callback that broadcasts info about fiat rates affecting subscribed currency 999 func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *common.CurrencyRatesTicker) { 1000 s.fiatRatesSubscriptionsLock.Lock() 1001 defer s.fiatRatesSubscriptionsLock.Unlock() 1002 for currency, rate := range ticker.Rates { 1003 s.broadcastTicker(currency, map[string]float32{currency: rate}, ticker) 1004 } 1005 s.broadcastTicker(allFiatRates, ticker.Rates, nil) 1006 } 1007 1008 func (s *WebsocketServer) getCurrentFiatRates(currencies []string, token string) (*api.FiatTicker, error) { 1009 ret, err := s.api.GetCurrentFiatRates(currencies, strings.ToLower(token)) 1010 return ret, err 1011 } 1012 1013 func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string, token string) (*api.FiatTickers, error) { 1014 ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies, strings.ToLower(token)) 1015 return ret, err 1016 } 1017 1018 func (s *WebsocketServer) getAvailableVsCurrencies(timestamp int64, token string) (*api.AvailableVsCurrencies, error) { 1019 ret, err := s.api.GetAvailableVsCurrencies(timestamp, strings.ToLower(token)) 1020 return ret, err 1021 }