github.com/llamaondrugs/blockbook@v0.3.2/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  }