github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/server/socketio.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 "time" 11 12 "github.com/cryptohub-digital/blockbook/api" 13 "github.com/cryptohub-digital/blockbook/bchain" 14 "github.com/cryptohub-digital/blockbook/common" 15 "github.com/cryptohub-digital/blockbook/db" 16 "github.com/cryptohub-digital/blockbook/fiat" 17 "github.com/golang/glog" 18 "github.com/juju/errors" 19 gosocketio "github.com/martinboehm/golang-socketio" 20 "github.com/martinboehm/golang-socketio/transport" 21 ) 22 23 // SocketIoServer is handle to SocketIoServer 24 type SocketIoServer struct { 25 server *gosocketio.Server 26 db *db.RocksDB 27 txCache *db.TxCache 28 chain bchain.BlockChain 29 chainParser bchain.BlockChainParser 30 mempool bchain.Mempool 31 metrics *common.Metrics 32 is *common.InternalState 33 api *api.Worker 34 } 35 36 // NewSocketIoServer creates new SocketIo interface to blockbook and returns its handle 37 func NewSocketIoServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*SocketIoServer, error) { 38 api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) 39 if err != nil { 40 return nil, err 41 } 42 43 server := gosocketio.NewServer(transport.GetDefaultWebsocketTransport()) 44 45 server.On(gosocketio.OnConnection, func(c *gosocketio.Channel) { 46 glog.Info("Client connected ", c.Id()) 47 metrics.SocketIOClients.Inc() 48 }) 49 50 server.On(gosocketio.OnDisconnection, func(c *gosocketio.Channel) { 51 glog.Info("Client disconnected ", c.Id()) 52 metrics.SocketIOClients.Dec() 53 }) 54 55 server.On(gosocketio.OnError, func(c *gosocketio.Channel) { 56 glog.Error("Client error ", c.Id()) 57 }) 58 59 type Message struct { 60 Name string `json:"name"` 61 Message string `json:"message"` 62 } 63 s := &SocketIoServer{ 64 server: server, 65 db: db, 66 txCache: txCache, 67 chain: chain, 68 chainParser: chain.GetChainParser(), 69 mempool: mempool, 70 metrics: metrics, 71 is: is, 72 api: api, 73 } 74 75 server.On("message", s.onMessage) 76 server.On("subscribe", s.onSubscribe) 77 78 return s, nil 79 } 80 81 // GetHandler returns socket.io http handler 82 func (s *SocketIoServer) GetHandler() http.Handler { 83 return s.server 84 } 85 86 type addrOpts struct { 87 Start int `json:"start"` 88 End int `json:"end"` 89 QueryMempoolOnly bool `json:"queryMempoolOnly"` 90 From int `json:"from"` 91 To int `json:"to"` 92 } 93 94 var onMessageHandlers = map[string]func(*SocketIoServer, json.RawMessage) (interface{}, error){ 95 "getAddressTxids": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 96 addr, opts, err := unmarshalGetAddressRequest(params) 97 if err == nil { 98 rv, err = s.getAddressTxids(addr, &opts) 99 } 100 return 101 }, 102 "getAddressHistory": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 103 addr, opts, err := unmarshalGetAddressRequest(params) 104 if err == nil { 105 rv, err = s.getAddressHistory(addr, &opts) 106 } 107 return 108 }, 109 "getBlockHeader": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 110 height, hash, err := unmarshalGetBlockHeader(params) 111 if err == nil { 112 rv, err = s.getBlockHeader(height, hash) 113 } 114 return 115 }, 116 "estimateSmartFee": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 117 blocks, conservative, err := unmarshalEstimateSmartFee(params) 118 if err == nil { 119 rv, err = s.estimateSmartFee(blocks, conservative) 120 } 121 return 122 }, 123 "estimateFee": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 124 blocks, err := unmarshalEstimateFee(params) 125 if err == nil { 126 rv, err = s.estimateFee(blocks) 127 } 128 return 129 }, 130 "getInfo": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 131 return s.getInfo() 132 }, 133 "getDetailedTransaction": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 134 txid, err := unmarshalGetDetailedTransaction(params) 135 if err == nil { 136 rv, err = s.getDetailedTransaction(txid) 137 } 138 return 139 }, 140 "sendTransaction": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 141 tx, err := unmarshalStringParameter(params) 142 if err == nil { 143 rv, err = s.sendTransaction(tx) 144 } 145 return 146 }, 147 "getMempoolEntry": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) { 148 txid, err := unmarshalStringParameter(params) 149 if err == nil { 150 rv, err = s.getMempoolEntry(txid) 151 } 152 return 153 }, 154 } 155 156 type resultError struct { 157 Error struct { 158 Message string `json:"message"` 159 } `json:"error"` 160 } 161 162 func (s *SocketIoServer) onMessage(c *gosocketio.Channel, req map[string]json.RawMessage) (rv interface{}) { 163 var err error 164 method := strings.Trim(string(req["method"]), "\"") 165 defer func() { 166 if r := recover(); r != nil { 167 glog.Error(c.Id(), " onMessage ", method, " recovered from panic: ", r) 168 debug.PrintStack() 169 e := resultError{} 170 e.Error.Message = "Internal error" 171 rv = e 172 } 173 s.metrics.SocketIOPendingRequests.With((common.Labels{"method": method})).Dec() 174 }() 175 t := time.Now() 176 params := req["params"] 177 s.metrics.SocketIOPendingRequests.With((common.Labels{"method": method})).Inc() 178 defer s.metrics.SocketIOReqDuration.With(common.Labels{"method": method}).Observe(float64(time.Since(t)) / 1e3) // in microseconds 179 f, ok := onMessageHandlers[method] 180 if ok { 181 rv, err = f(s, params) 182 } else { 183 err = errors.New("unknown method") 184 } 185 if err == nil { 186 glog.V(1).Info(c.Id(), " onMessage ", method, " success") 187 s.metrics.SocketIORequests.With(common.Labels{"method": method, "status": "success"}).Inc() 188 return rv 189 } 190 glog.Error(c.Id(), " onMessage ", method, ": ", errors.ErrorStack(err), ", data ", string(params)) 191 s.metrics.SocketIORequests.With(common.Labels{"method": method, "status": "failure"}).Inc() 192 e := resultError{} 193 e.Error.Message = err.Error() 194 return e 195 } 196 197 func unmarshalGetAddressRequest(params []byte) (addr []string, opts addrOpts, err error) { 198 var p []json.RawMessage 199 err = json.Unmarshal(params, &p) 200 if err != nil { 201 return 202 } 203 if len(p) != 2 { 204 err = errors.New("incorrect number of parameters") 205 return 206 } 207 err = json.Unmarshal(p[0], &addr) 208 if err != nil { 209 return 210 } 211 err = json.Unmarshal(p[1], &opts) 212 return 213 } 214 215 type resultAddressTxids struct { 216 Result []string `json:"result"` 217 } 218 219 func (s *SocketIoServer) getAddressTxids(addr []string, opts *addrOpts) (res resultAddressTxids, err error) { 220 txids := make([]string, 0, 8) 221 lower, higher := uint32(opts.End), uint32(opts.Start) 222 for _, address := range addr { 223 if !opts.QueryMempoolOnly { 224 err = s.db.GetTransactions(address, lower, higher, func(txid string, height uint32, indexes []int32) error { 225 txids = append(txids, txid) 226 return nil 227 }) 228 if err != nil { 229 return res, err 230 } 231 } else { 232 o, err := s.mempool.GetTransactions(address) 233 if err != nil { 234 return res, err 235 } 236 for _, m := range o { 237 txids = append(txids, m.Txid) 238 } 239 } 240 } 241 res.Result = api.GetUniqueTxids(txids) 242 return res, nil 243 } 244 245 type addressHistoryIndexes struct { 246 InputIndexes []int `json:"inputIndexes"` 247 OutputIndexes []int `json:"outputIndexes"` 248 } 249 250 type txInputs struct { 251 Txid *string `json:"txid"` 252 OutputIndex int `json:"outputIndex"` 253 Script *string `json:"script"` 254 // ScriptAsm *string `json:"scriptAsm"` 255 Sequence int64 `json:"sequence"` 256 Address *string `json:"address"` 257 Satoshis int64 `json:"satoshis"` 258 } 259 260 type txOutputs struct { 261 Satoshis int64 `json:"satoshis"` 262 Script *string `json:"script"` 263 // ScriptAsm *string `json:"scriptAsm"` 264 // SpentTxID *string `json:"spentTxId,omitempty"` 265 // SpentIndex int `json:"spentIndex,omitempty"` 266 // SpentHeight int `json:"spentHeight,omitempty"` 267 Address *string `json:"address"` 268 } 269 270 type resTx struct { 271 Hex string `json:"hex"` 272 // BlockHash string `json:"blockHash,omitempty"` 273 Height int `json:"height"` 274 BlockTimestamp int64 `json:"blockTimestamp,omitempty"` 275 Version int `json:"version"` 276 Hash string `json:"hash"` 277 Locktime int `json:"locktime,omitempty"` 278 // Size int `json:"size,omitempty"` 279 Inputs []txInputs `json:"inputs"` 280 InputSatoshis int64 `json:"inputSatoshis,omitempty"` 281 Outputs []txOutputs `json:"outputs"` 282 OutputSatoshis int64 `json:"outputSatoshis,omitempty"` 283 FeeSatoshis int64 `json:"feeSatoshis,omitempty"` 284 } 285 286 type addressHistoryItem struct { 287 Addresses map[string]*addressHistoryIndexes `json:"addresses"` 288 Satoshis int64 `json:"satoshis"` 289 Confirmations int `json:"confirmations"` 290 Tx resTx `json:"tx"` 291 } 292 293 type resultGetAddressHistory struct { 294 Result struct { 295 TotalCount int `json:"totalCount"` 296 Items []addressHistoryItem `json:"items"` 297 } `json:"result"` 298 } 299 300 func txToResTx(tx *api.Tx) resTx { 301 inputs := make([]txInputs, len(tx.Vin)) 302 for i := range tx.Vin { 303 vin := &tx.Vin[i] 304 txid := vin.Txid 305 script := vin.Hex 306 input := txInputs{ 307 Txid: &txid, 308 Script: &script, 309 Sequence: int64(vin.Sequence), 310 OutputIndex: int(vin.Vout), 311 Satoshis: vin.ValueSat.AsInt64(), 312 } 313 if len(vin.Addresses) > 0 { 314 a := vin.Addresses[0] 315 input.Address = &a 316 } 317 inputs[i] = input 318 } 319 outputs := make([]txOutputs, len(tx.Vout)) 320 for i := range tx.Vout { 321 vout := &tx.Vout[i] 322 script := vout.Hex 323 output := txOutputs{ 324 Satoshis: vout.ValueSat.AsInt64(), 325 Script: &script, 326 } 327 if len(vout.Addresses) > 0 { 328 a := vout.Addresses[0] 329 output.Address = &a 330 } 331 outputs[i] = output 332 } 333 var h int 334 var blocktime int64 335 if tx.Confirmations == 0 { 336 h = -1 337 } else { 338 h = int(tx.Blockheight) 339 blocktime = tx.Blocktime 340 } 341 return resTx{ 342 BlockTimestamp: blocktime, 343 FeeSatoshis: tx.FeesSat.AsInt64(), 344 Hash: tx.Txid, 345 Height: h, 346 Hex: tx.Hex, 347 Inputs: inputs, 348 InputSatoshis: tx.ValueInSat.AsInt64(), 349 Locktime: int(tx.Locktime), 350 Outputs: outputs, 351 OutputSatoshis: tx.ValueOutSat.AsInt64(), 352 Version: int(tx.Version), 353 } 354 } 355 356 func addressInSlice(s, t []string) string { 357 for _, sa := range s { 358 for _, ta := range t { 359 if ta == sa { 360 return sa 361 } 362 } 363 } 364 return "" 365 } 366 367 func (s *SocketIoServer) getAddressesFromVout(vout *bchain.Vout) ([]string, error) { 368 addrDesc, err := s.chainParser.GetAddrDescFromVout(vout) 369 if err != nil { 370 return nil, err 371 } 372 voutAddr, _, err := s.chainParser.GetAddressesFromAddrDesc(addrDesc) 373 if err != nil { 374 return nil, err 375 } 376 return voutAddr, nil 377 } 378 379 func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res resultGetAddressHistory, err error) { 380 txr, err := s.getAddressTxids(addr, opts) 381 if err != nil { 382 return 383 } 384 txids := txr.Result 385 res.Result.TotalCount = len(txids) 386 res.Result.Items = make([]addressHistoryItem, 0, 8) 387 to := len(txids) 388 if to > opts.To { 389 to = opts.To 390 } 391 for txi := opts.From; txi < to; txi++ { 392 tx, err := s.api.GetTransaction(txids[txi], false, false) 393 if err != nil { 394 return res, err 395 } 396 ads := make(map[string]*addressHistoryIndexes) 397 var totalSat big.Int 398 for i := range tx.Vin { 399 vin := &tx.Vin[i] 400 a := addressInSlice(vin.Addresses, addr) 401 if a != "" { 402 hi := ads[a] 403 if hi == nil { 404 hi = &addressHistoryIndexes{OutputIndexes: []int{}} 405 ads[a] = hi 406 } 407 hi.InputIndexes = append(hi.InputIndexes, int(vin.N)) 408 if vin.ValueSat != nil { 409 totalSat.Sub(&totalSat, (*big.Int)(vin.ValueSat)) 410 } 411 } 412 } 413 for i := range tx.Vout { 414 vout := &tx.Vout[i] 415 a := addressInSlice(vout.Addresses, addr) 416 if a != "" { 417 hi := ads[a] 418 if hi == nil { 419 hi = &addressHistoryIndexes{InputIndexes: []int{}} 420 ads[a] = hi 421 } 422 hi.OutputIndexes = append(hi.OutputIndexes, int(vout.N)) 423 if vout.ValueSat != nil { 424 totalSat.Add(&totalSat, (*big.Int)(vout.ValueSat)) 425 } 426 } 427 } 428 ahi := addressHistoryItem{} 429 ahi.Addresses = ads 430 ahi.Confirmations = int(tx.Confirmations) 431 ahi.Satoshis = totalSat.Int64() 432 ahi.Tx = txToResTx(tx) 433 res.Result.Items = append(res.Result.Items, ahi) 434 // } 435 } 436 return 437 } 438 439 func unmarshalArray(params []byte, np int) (p []interface{}, err error) { 440 err = json.Unmarshal(params, &p) 441 if err != nil { 442 return 443 } 444 if len(p) != np { 445 err = errors.New("incorrect number of parameters") 446 return 447 } 448 return 449 } 450 451 func unmarshalGetBlockHeader(params []byte) (height uint32, hash string, err error) { 452 p, err := unmarshalArray(params, 1) 453 if err != nil { 454 return 455 } 456 fheight, ok := p[0].(float64) 457 if ok { 458 return uint32(fheight), "", nil 459 } 460 hash, ok = p[0].(string) 461 if ok { 462 return 463 } 464 err = errors.New("incorrect parameter") 465 return 466 } 467 468 type resultGetBlockHeader struct { 469 Result struct { 470 Hash string `json:"hash"` 471 Version int `json:"version"` 472 Confirmations int `json:"confirmations"` 473 Height int `json:"height"` 474 ChainWork string `json:"chainWork"` 475 NextHash string `json:"nextHash"` 476 MerkleRoot string `json:"merkleRoot"` 477 Time int `json:"time"` 478 MedianTime int `json:"medianTime"` 479 Nonce int `json:"nonce"` 480 Bits string `json:"bits"` 481 Difficulty float64 `json:"difficulty"` 482 } `json:"result"` 483 } 484 485 func (s *SocketIoServer) getBlockHeader(height uint32, hash string) (res resultGetBlockHeader, err error) { 486 if hash == "" { 487 // trezor is interested only in hash 488 hash, err = s.db.GetBlockHash(height) 489 if err != nil { 490 return 491 } 492 res.Result.Hash = hash 493 return 494 } 495 bh, err := s.chain.GetBlockHeader(hash) 496 if err != nil { 497 return 498 } 499 res.Result.Hash = bh.Hash 500 res.Result.Confirmations = bh.Confirmations 501 res.Result.Height = int(bh.Height) 502 res.Result.NextHash = bh.Next 503 return 504 } 505 506 func unmarshalEstimateSmartFee(params []byte) (blocks int, conservative bool, err error) { 507 p, err := unmarshalArray(params, 2) 508 if err != nil { 509 return 510 } 511 fblocks, ok := p[0].(float64) 512 if !ok { 513 err = errors.New("Invalid parameter blocks") 514 return 515 } 516 blocks = int(fblocks) 517 conservative, ok = p[1].(bool) 518 if !ok { 519 err = errors.New("Invalid parameter conservative") 520 return 521 } 522 return 523 } 524 525 type resultEstimateSmartFee struct { 526 // for compatibility reasons use float64 527 Result float64 `json:"result"` 528 } 529 530 func (s *SocketIoServer) estimateSmartFee(blocks int, conservative bool) (res resultEstimateSmartFee, err error) { 531 fee, err := s.chain.EstimateSmartFee(blocks, conservative) 532 if err != nil { 533 return 534 } 535 res.Result, err = strconv.ParseFloat(s.chainParser.AmountToDecimalString(&fee), 64) 536 return 537 } 538 539 func unmarshalEstimateFee(params []byte) (blocks int, err error) { 540 p, err := unmarshalArray(params, 1) 541 if err != nil { 542 return 543 } 544 fblocks, ok := p[0].(float64) 545 if !ok { 546 err = errors.New("Invalid parameter nblocks") 547 return 548 } 549 blocks = int(fblocks) 550 return 551 } 552 553 type resultEstimateFee struct { 554 // for compatibility reasons use float64 555 Result float64 `json:"result"` 556 } 557 558 func (s *SocketIoServer) estimateFee(blocks int) (res resultEstimateFee, err error) { 559 fee, err := s.chain.EstimateFee(blocks) 560 if err != nil { 561 return 562 } 563 res.Result, err = strconv.ParseFloat(s.chainParser.AmountToDecimalString(&fee), 64) 564 return 565 } 566 567 type resultGetInfo struct { 568 Result struct { 569 Version int `json:"version,omitempty"` 570 ProtocolVersion int `json:"protocolVersion,omitempty"` 571 Blocks int `json:"blocks"` 572 TimeOffset int `json:"timeOffset,omitempty"` 573 Connections int `json:"connections,omitempty"` 574 Proxy string `json:"proxy,omitempty"` 575 Difficulty float64 `json:"difficulty,omitempty"` 576 Testnet bool `json:"testnet"` 577 RelayFee float64 `json:"relayFee,omitempty"` 578 Errors string `json:"errors,omitempty"` 579 Network string `json:"network,omitempty"` 580 Subversion string `json:"subversion,omitempty"` 581 LocalServices string `json:"localServices,omitempty"` 582 CoinName string `json:"coin_name,omitempty"` 583 About string `json:"about,omitempty"` 584 } `json:"result"` 585 } 586 587 func (s *SocketIoServer) getInfo() (res resultGetInfo, err error) { 588 _, height, _, _ := s.is.GetSyncState() 589 res.Result.Blocks = int(height) 590 res.Result.Testnet = s.chain.IsTestnet() 591 res.Result.Network = s.chain.GetNetworkName() 592 res.Result.Subversion = s.chain.GetSubversion() 593 res.Result.CoinName = s.chain.GetCoinName() 594 res.Result.About = api.Text.BlockbookAbout 595 return 596 } 597 598 func unmarshalStringParameter(params []byte) (s string, err error) { 599 p, err := unmarshalArray(params, 1) 600 if err != nil { 601 return 602 } 603 s, ok := p[0].(string) 604 if ok { 605 return 606 } 607 err = errors.New("incorrect parameter") 608 return 609 } 610 611 func unmarshalGetDetailedTransaction(params []byte) (txid string, err error) { 612 var p []json.RawMessage 613 err = json.Unmarshal(params, &p) 614 if err != nil { 615 return 616 } 617 if len(p) != 1 { 618 err = errors.New("incorrect number of parameters") 619 return 620 } 621 err = json.Unmarshal(p[0], &txid) 622 if err != nil { 623 return 624 } 625 return 626 } 627 628 type resultGetDetailedTransaction struct { 629 Result resTx `json:"result"` 630 } 631 632 func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetailedTransaction, err error) { 633 tx, err := s.api.GetTransaction(txid, false, false) 634 if err != nil { 635 return res, err 636 } 637 res.Result = txToResTx(tx) 638 return 639 } 640 641 func (s *SocketIoServer) sendTransaction(tx string) (res resultSendTransaction, err error) { 642 txid, err := s.chain.SendRawTransaction(tx) 643 if err != nil { 644 return res, err 645 } 646 res.Result = txid 647 return 648 } 649 650 type resultGetMempoolEntry struct { 651 Result *bchain.MempoolEntry `json:"result"` 652 } 653 654 func (s *SocketIoServer) getMempoolEntry(txid string) (res resultGetMempoolEntry, err error) { 655 entry, err := s.chain.GetMempoolEntry(txid) 656 if err != nil { 657 return res, err 658 } 659 res.Result = entry 660 return 661 } 662 663 // onSubscribe expects two event subscriptions based on the req parameter (including the doublequotes): 664 // "bitcoind/hashblock" 665 // "bitcoind/addresstxid",["2MzTmvPJLZaLzD9XdN3jMtQA5NexC3rAPww","2NAZRJKr63tSdcTxTN3WaE9ZNDyXy6PgGuv"] 666 func (s *SocketIoServer) onSubscribe(c *gosocketio.Channel, req []byte) interface{} { 667 defer func() { 668 if r := recover(); r != nil { 669 glog.Error(c.Id(), " onSubscribe recovered from panic: ", r) 670 debug.PrintStack() 671 } 672 }() 673 674 onError := func(id, sc, err, detail string) { 675 glog.Error(id, " onSubscribe ", err, ": ", detail) 676 s.metrics.SocketIOSubscribes.With(common.Labels{"channel": sc, "status": "failure"}).Inc() 677 } 678 679 r := string(req) 680 glog.V(1).Info(c.Id(), " onSubscribe ", r) 681 var sc string 682 i := strings.Index(r, "\",[") 683 if i > 0 { 684 var addrs []string 685 sc = r[1:i] 686 if sc != "bitcoind/addresstxid" { 687 onError(c.Id(), sc, "invalid data", "expecting bitcoind/addresstxid, req: "+r) 688 return nil 689 } 690 err := json.Unmarshal([]byte(r[i+2:]), &addrs) 691 if err != nil { 692 onError(c.Id(), sc, "invalid data", err.Error()+", req: "+r) 693 return nil 694 } 695 // normalize the addresses to AddressDescriptor 696 descs := make([]bchain.AddressDescriptor, len(addrs)) 697 for i, a := range addrs { 698 d, err := s.chainParser.GetAddrDescFromAddress(a) 699 if err != nil { 700 onError(c.Id(), sc, "invalid address "+a, err.Error()+", req: "+r) 701 return nil 702 } 703 descs[i] = d 704 } 705 for _, d := range descs { 706 c.Join("bitcoind/addresstxid-" + string(d)) 707 } 708 } else { 709 sc = r[1 : len(r)-1] 710 if sc != "bitcoind/hashblock" { 711 onError(c.Id(), sc, "invalid data", "expecting bitcoind/hashblock, req: "+r) 712 return nil 713 } 714 c.Join(sc) 715 } 716 s.metrics.SocketIOSubscribes.With(common.Labels{"channel": sc, "status": "success"}).Inc() 717 return nil 718 } 719 720 func (s *SocketIoServer) onNewBlockHashAsync(hash string) { 721 c := s.server.BroadcastTo("bitcoind/hashblock", "bitcoind/hashblock", hash) 722 glog.Info("broadcasting new block hash ", hash, " to ", c, " channels") 723 } 724 725 // OnNewBlockHash notifies users subscribed to bitcoind/hashblock about new block 726 func (s *SocketIoServer) OnNewBlockHash(hash string) { 727 go s.onNewBlockHashAsync(hash) 728 } 729 730 // OnNewTxAddr notifies users subscribed to bitcoind/addresstxid about new block 731 func (s *SocketIoServer) OnNewTxAddr(txid string, desc bchain.AddressDescriptor) { 732 addr, searchable, err := s.chainParser.GetAddressesFromAddrDesc(desc) 733 if err != nil { 734 glog.Error("GetAddressesFromAddrDesc error ", err, " for descriptor ", desc) 735 } else if searchable && len(addr) == 1 { 736 data := map[string]interface{}{"address": addr[0], "txid": txid} 737 c := s.server.BroadcastTo("bitcoind/addresstxid-"+string(desc), "bitcoind/addresstxid", data) 738 if c > 0 { 739 glog.Info("broadcasting new txid ", txid, " for addr ", addr[0], " to ", c, " channels") 740 } 741 } 742 }