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  }