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