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