github.com/rumhocker/blockbook@v0.3.2/server/websocket.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  	"sync"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"github.com/golang/glog"
    19  	"github.com/gorilla/websocket"
    20  	"github.com/juju/errors"
    21  )
    22  
    23  const upgradeFailed = "Upgrade failed: "
    24  const outChannelSize = 500
    25  const defaultTimeout = 60 * time.Second
    26  
    27  // allRates is a special "currency" parameter that means all available currencies
    28  const allFiatRates = "!ALL!"
    29  
    30  var (
    31  	// ErrorMethodNotAllowed is returned when client tries to upgrade method other than GET
    32  	ErrorMethodNotAllowed = errors.New("Method not allowed")
    33  
    34  	connectionCounter uint64
    35  )
    36  
    37  type websocketReq struct {
    38  	ID     string          `json:"id"`
    39  	Method string          `json:"method"`
    40  	Params json.RawMessage `json:"params"`
    41  }
    42  
    43  type websocketRes struct {
    44  	ID   string      `json:"id"`
    45  	Data interface{} `json:"data"`
    46  }
    47  
    48  type websocketChannel struct {
    49  	id            uint64
    50  	conn          *websocket.Conn
    51  	out           chan *websocketRes
    52  	ip            string
    53  	requestHeader http.Header
    54  	alive         bool
    55  	aliveLock     sync.Mutex
    56  }
    57  
    58  // WebsocketServer is a handle to websocket server
    59  type WebsocketServer struct {
    60  	socket                     *websocket.Conn
    61  	upgrader                   *websocket.Upgrader
    62  	db                         *db.RocksDB
    63  	txCache                    *db.TxCache
    64  	chain                      bchain.BlockChain
    65  	chainParser                bchain.BlockChainParser
    66  	mempool                    bchain.Mempool
    67  	metrics                    *common.Metrics
    68  	is                         *common.InternalState
    69  	api                        *api.Worker
    70  	block0hash                 string
    71  	newBlockSubscriptions      map[*websocketChannel]string
    72  	newBlockSubscriptionsLock  sync.Mutex
    73  	addressSubscriptions       map[string]map[*websocketChannel]string
    74  	addressSubscriptionsLock   sync.Mutex
    75  	fiatRatesSubscriptions     map[string]map[*websocketChannel]string
    76  	fiatRatesSubscriptionsLock sync.Mutex
    77  }
    78  
    79  // NewWebsocketServer creates new websocket interface to blockbook and returns its handle
    80  func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*WebsocketServer, error) {
    81  	api, err := api.NewWorker(db, chain, mempool, txCache, is)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	b0, err := db.GetBlockHash(0)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	s := &WebsocketServer{
    90  		upgrader: &websocket.Upgrader{
    91  			ReadBufferSize:  1024 * 32,
    92  			WriteBufferSize: 1024 * 32,
    93  			CheckOrigin:     checkOrigin,
    94  		},
    95  		db:                     db,
    96  		txCache:                txCache,
    97  		chain:                  chain,
    98  		chainParser:            chain.GetChainParser(),
    99  		mempool:                mempool,
   100  		metrics:                metrics,
   101  		is:                     is,
   102  		api:                    api,
   103  		block0hash:             b0,
   104  		newBlockSubscriptions:  make(map[*websocketChannel]string),
   105  		addressSubscriptions:   make(map[string]map[*websocketChannel]string),
   106  		fiatRatesSubscriptions: make(map[string]map[*websocketChannel]string),
   107  	}
   108  	return s, nil
   109  }
   110  
   111  // allow all origins
   112  func checkOrigin(r *http.Request) bool {
   113  	return true
   114  }
   115  
   116  // ServeHTTP sets up handler of websocket channel
   117  func (s *WebsocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   118  	if r.Method != "GET" {
   119  		http.Error(w, upgradeFailed+ErrorMethodNotAllowed.Error(), 503)
   120  		return
   121  	}
   122  	conn, err := s.upgrader.Upgrade(w, r, nil)
   123  	if err != nil {
   124  		http.Error(w, upgradeFailed+err.Error(), 503)
   125  		return
   126  	}
   127  	c := &websocketChannel{
   128  		id:            atomic.AddUint64(&connectionCounter, 1),
   129  		conn:          conn,
   130  		out:           make(chan *websocketRes, outChannelSize),
   131  		ip:            r.RemoteAddr,
   132  		requestHeader: r.Header,
   133  		alive:         true,
   134  	}
   135  	go s.inputLoop(c)
   136  	go s.outputLoop(c)
   137  	s.onConnect(c)
   138  }
   139  
   140  // GetHandler returns http handler
   141  func (s *WebsocketServer) GetHandler() http.Handler {
   142  	return s
   143  }
   144  
   145  func (s *WebsocketServer) closeChannel(c *websocketChannel) {
   146  	c.aliveLock.Lock()
   147  	defer c.aliveLock.Unlock()
   148  	if c.alive {
   149  		c.conn.Close()
   150  		c.alive = false
   151  		//clean out
   152  		close(c.out)
   153  		for len(c.out) > 0 {
   154  			<-c.out
   155  		}
   156  		s.onDisconnect(c)
   157  	}
   158  }
   159  
   160  func (c *websocketChannel) IsAlive() bool {
   161  	c.aliveLock.Lock()
   162  	defer c.aliveLock.Unlock()
   163  	return c.alive
   164  }
   165  
   166  func (s *WebsocketServer) inputLoop(c *websocketChannel) {
   167  	defer func() {
   168  		if r := recover(); r != nil {
   169  			glog.Error("recovered from panic: ", r, ", ", c.id)
   170  			debug.PrintStack()
   171  			s.closeChannel(c)
   172  		}
   173  	}()
   174  	for {
   175  		t, d, err := c.conn.ReadMessage()
   176  		if err != nil {
   177  			s.closeChannel(c)
   178  			return
   179  		}
   180  		switch t {
   181  		case websocket.TextMessage:
   182  			var req websocketReq
   183  			err := json.Unmarshal(d, &req)
   184  			if err != nil {
   185  				glog.Error("Error parsing message from ", c.id, ", ", string(d), ", ", err)
   186  				s.closeChannel(c)
   187  				return
   188  			}
   189  			go s.onRequest(c, &req)
   190  		case websocket.BinaryMessage:
   191  			glog.Error("Binary message received from ", c.id, ", ", c.ip)
   192  			s.closeChannel(c)
   193  			return
   194  		case websocket.PingMessage:
   195  			c.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(defaultTimeout))
   196  			break
   197  		case websocket.CloseMessage:
   198  			s.closeChannel(c)
   199  			return
   200  		case websocket.PongMessage:
   201  			// do nothing
   202  		}
   203  	}
   204  }
   205  
   206  func (s *WebsocketServer) outputLoop(c *websocketChannel) {
   207  	for m := range c.out {
   208  		err := c.conn.WriteJSON(m)
   209  		if err != nil {
   210  			glog.Error("Error sending message to ", c.id, ", ", err)
   211  			s.closeChannel(c)
   212  		}
   213  	}
   214  }
   215  
   216  func (s *WebsocketServer) onConnect(c *websocketChannel) {
   217  	glog.Info("Client connected ", c.id, ", ", c.ip)
   218  	s.metrics.WebsocketClients.Inc()
   219  }
   220  
   221  func (s *WebsocketServer) onDisconnect(c *websocketChannel) {
   222  	s.unsubscribeNewBlock(c)
   223  	s.unsubscribeAddresses(c)
   224  	s.unsubscribeFiatRates(c)
   225  	glog.Info("Client disconnected ", c.id, ", ", c.ip)
   226  	s.metrics.WebsocketClients.Dec()
   227  }
   228  
   229  var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *websocketReq) (interface{}, error){
   230  	"getAccountInfo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   231  		r, err := unmarshalGetAccountInfoRequest(req.Params)
   232  		if err == nil {
   233  			rv, err = s.getAccountInfo(r)
   234  		}
   235  		return
   236  	},
   237  	"getInfo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   238  		return s.getInfo()
   239  	},
   240  	"getBlockHash": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   241  		r := struct {
   242  			Height int `json:"height"`
   243  		}{}
   244  		err = json.Unmarshal(req.Params, &r)
   245  		if err == nil {
   246  			rv, err = s.getBlockHash(r.Height)
   247  		}
   248  		return
   249  	},
   250  	"getAccountUtxo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   251  		r := struct {
   252  			Descriptor string `json:"descriptor"`
   253  		}{}
   254  		err = json.Unmarshal(req.Params, &r)
   255  		if err == nil {
   256  			rv, err = s.getAccountUtxo(r.Descriptor)
   257  		}
   258  		return
   259  	},
   260  	"getBalanceHistory": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   261  		r := struct {
   262  			Descriptor string   `json:"descriptor"`
   263  			From       int64    `json:"from"`
   264  			To         int64    `json:"to"`
   265  			Currencies []string `json:"currencies"`
   266  			Gap        int      `json:"gap"`
   267  			GroupBy    uint32   `json:"groupBy"`
   268  		}{}
   269  		err = json.Unmarshal(req.Params, &r)
   270  		if err == nil {
   271  			if r.From <= 0 {
   272  				r.From = 0
   273  			}
   274  			if r.To <= 0 {
   275  				r.To = 0
   276  			}
   277  			if r.GroupBy <= 0 {
   278  				r.GroupBy = 3600
   279  			}
   280  			rv, err = s.api.GetXpubBalanceHistory(r.Descriptor, r.From, r.To, r.Currencies, r.Gap, r.GroupBy)
   281  			if err != nil {
   282  				rv, err = s.api.GetBalanceHistory(r.Descriptor, r.From, r.To, r.Currencies, r.GroupBy)
   283  			}
   284  		}
   285  		return
   286  	},
   287  	"getTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   288  		r := struct {
   289  			Txid string `json:"txid"`
   290  		}{}
   291  		err = json.Unmarshal(req.Params, &r)
   292  		if err == nil {
   293  			rv, err = s.getTransaction(r.Txid)
   294  		}
   295  		return
   296  	},
   297  	"getTransactionSpecific": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   298  		r := struct {
   299  			Txid string `json:"txid"`
   300  		}{}
   301  		err = json.Unmarshal(req.Params, &r)
   302  		if err == nil {
   303  			rv, err = s.getTransactionSpecific(r.Txid)
   304  		}
   305  		return
   306  	},
   307  	"estimateFee": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   308  		return s.estimateFee(c, req.Params)
   309  	},
   310  	"sendTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   311  		r := struct {
   312  			Hex string `json:"hex"`
   313  		}{}
   314  		err = json.Unmarshal(req.Params, &r)
   315  		if err == nil {
   316  			rv, err = s.sendTransaction(r.Hex)
   317  		}
   318  		return
   319  	},
   320  	"subscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   321  		return s.subscribeNewBlock(c, req)
   322  	},
   323  	"unsubscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   324  		return s.unsubscribeNewBlock(c)
   325  	},
   326  	"subscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   327  		ad, err := s.unmarshalAddresses(req.Params)
   328  		if err == nil {
   329  			rv, err = s.subscribeAddresses(c, ad, req)
   330  		}
   331  		return
   332  	},
   333  	"unsubscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   334  		return s.unsubscribeAddresses(c)
   335  	},
   336  	"subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   337  		r := struct {
   338  			Currency string `json:"currency"`
   339  		}{}
   340  		err = json.Unmarshal(req.Params, &r)
   341  		if err != nil {
   342  			return nil, err
   343  		}
   344  		return s.subscribeFiatRates(c, strings.ToLower(r.Currency), req)
   345  	},
   346  	"unsubscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   347  		return s.unsubscribeFiatRates(c)
   348  	},
   349  	"ping": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   350  		r := struct{}{}
   351  		return r, nil
   352  	},
   353  	"getCurrentFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   354  		r := struct {
   355  			Currencies []string `json:"currencies"`
   356  		}{}
   357  		err = json.Unmarshal(req.Params, &r)
   358  		if err == nil {
   359  			rv, err = s.getCurrentFiatRates(r.Currencies)
   360  		}
   361  		return
   362  	},
   363  	"getFiatRatesForTimestamps": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   364  		r := struct {
   365  			Timestamps []int64  `json:"timestamps"`
   366  			Currencies []string `json:"currencies"`
   367  		}{}
   368  		err = json.Unmarshal(req.Params, &r)
   369  		if err == nil {
   370  			rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies)
   371  		}
   372  		return
   373  	},
   374  	"getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
   375  		r := struct {
   376  			Timestamp int64 `json:"timestamp"`
   377  		}{}
   378  		err = json.Unmarshal(req.Params, &r)
   379  		if err == nil {
   380  			rv, err = s.getFiatRatesTickersList(r.Timestamp)
   381  		}
   382  		return
   383  	},
   384  }
   385  
   386  func sendResponse(c *websocketChannel, req *websocketReq, data interface{}) {
   387  	defer func() {
   388  		if r := recover(); r != nil {
   389  			glog.Error("Client ", c.id, ", onRequest ", req.Method, " recovered from panic: ", r)
   390  		}
   391  	}()
   392  	c.out <- &websocketRes{
   393  		ID:   req.ID,
   394  		Data: data,
   395  	}
   396  }
   397  
   398  func (s *WebsocketServer) onRequest(c *websocketChannel, req *websocketReq) {
   399  	var err error
   400  	var data interface{}
   401  	defer func() {
   402  		if r := recover(); r != nil {
   403  			glog.Error("Client ", c.id, ", onRequest ", req.Method, " recovered from panic: ", r)
   404  			debug.PrintStack()
   405  			e := resultError{}
   406  			e.Error.Message = "Internal error"
   407  			data = e
   408  		}
   409  		// nil data means no response
   410  		if data != nil {
   411  			sendResponse(c, req, data)
   412  		}
   413  	}()
   414  	t := time.Now()
   415  	defer s.metrics.WebsocketReqDuration.With(common.Labels{"method": req.Method}).Observe(float64(time.Since(t)) / 1e3) // in microseconds
   416  	f, ok := requestHandlers[req.Method]
   417  	if ok {
   418  		data, err = f(s, c, req)
   419  	} else {
   420  		err = errors.New("unknown method")
   421  	}
   422  	if err == nil {
   423  		glog.V(1).Info("Client ", c.id, " onRequest ", req.Method, " success")
   424  		s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": "success"}).Inc()
   425  	} else {
   426  		glog.Error("Client ", c.id, " onMessage ", req.Method, ": ", errors.ErrorStack(err), ", data ", string(req.Params))
   427  		s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": "failure"}).Inc()
   428  		e := resultError{}
   429  		e.Error.Message = err.Error()
   430  		data = e
   431  	}
   432  }
   433  
   434  type accountInfoReq struct {
   435  	Descriptor     string `json:"descriptor"`
   436  	Details        string `json:"details"`
   437  	Tokens         string `json:"tokens"`
   438  	PageSize       int    `json:"pageSize"`
   439  	Page           int    `json:"page"`
   440  	FromHeight     int    `json:"from"`
   441  	ToHeight       int    `json:"to"`
   442  	ContractFilter string `json:"contractFilter"`
   443  	Gap            int    `json:"gap"`
   444  }
   445  
   446  func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) {
   447  	var r accountInfoReq
   448  	err := json.Unmarshal(params, &r)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  	return &r, nil
   453  }
   454  
   455  func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) {
   456  	var opt api.AccountDetails
   457  	switch req.Details {
   458  	case "tokens":
   459  		opt = api.AccountDetailsTokens
   460  	case "tokenBalances":
   461  		opt = api.AccountDetailsTokenBalances
   462  	case "txids":
   463  		opt = api.AccountDetailsTxidHistory
   464  	case "txslight":
   465  		opt = api.AccountDetailsTxHistoryLight
   466  	case "txs":
   467  		opt = api.AccountDetailsTxHistory
   468  	default:
   469  		opt = api.AccountDetailsBasic
   470  	}
   471  	var tokensToReturn api.TokensToReturn
   472  	switch req.Tokens {
   473  	case "used":
   474  		tokensToReturn = api.TokensToReturnUsed
   475  	case "nonzero":
   476  		tokensToReturn = api.TokensToReturnNonzeroBalance
   477  	default:
   478  		tokensToReturn = api.TokensToReturnDerived
   479  	}
   480  	filter := api.AddressFilter{
   481  		FromHeight:     uint32(req.FromHeight),
   482  		ToHeight:       uint32(req.ToHeight),
   483  		Contract:       req.ContractFilter,
   484  		Vout:           api.AddressFilterVoutOff,
   485  		TokensToReturn: tokensToReturn,
   486  	}
   487  	if req.PageSize == 0 {
   488  		req.PageSize = txsOnPage
   489  	}
   490  	a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, req.Gap)
   491  	if err != nil {
   492  		return s.api.GetAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter)
   493  	}
   494  	return a, nil
   495  }
   496  
   497  func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error) {
   498  	utxo, err := s.api.GetXpubUtxo(descriptor, false, 0)
   499  	if err != nil {
   500  		return s.api.GetAddressUtxo(descriptor, false)
   501  	}
   502  	return utxo, nil
   503  }
   504  
   505  func (s *WebsocketServer) getTransaction(txid string) (interface{}, error) {
   506  	return s.api.GetTransaction(txid, false, false)
   507  }
   508  
   509  func (s *WebsocketServer) getTransactionSpecific(txid string) (interface{}, error) {
   510  	return s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid})
   511  }
   512  
   513  func (s *WebsocketServer) getInfo() (interface{}, error) {
   514  	vi := common.GetVersionInfo()
   515  	height, hash, err := s.db.GetBestBlock()
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  	type info struct {
   520  		Name       string `json:"name"`
   521  		Shortcut   string `json:"shortcut"`
   522  		Decimals   int    `json:"decimals"`
   523  		Version    string `json:"version"`
   524  		BestHeight int    `json:"bestHeight"`
   525  		BestHash   string `json:"bestHash"`
   526  		Block0Hash string `json:"block0Hash"`
   527  		Testnet    bool   `json:"testnet"`
   528  	}
   529  	return &info{
   530  		Name:       s.is.Coin,
   531  		Shortcut:   s.is.CoinShortcut,
   532  		Decimals:   s.chainParser.AmountDecimals(),
   533  		BestHeight: int(height),
   534  		BestHash:   hash,
   535  		Version:    vi.Version,
   536  		Block0Hash: s.block0hash,
   537  		Testnet:    s.chain.IsTestnet(),
   538  	}, nil
   539  }
   540  
   541  func (s *WebsocketServer) getBlockHash(height int) (interface{}, error) {
   542  	h, err := s.db.GetBlockHash(uint32(height))
   543  	if err != nil {
   544  		return nil, err
   545  	}
   546  	type hash struct {
   547  		Hash string `json:"hash"`
   548  	}
   549  	return &hash{
   550  		Hash: h,
   551  	}, nil
   552  }
   553  
   554  func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (interface{}, error) {
   555  	type estimateFeeReq struct {
   556  		Blocks   []int                  `json:"blocks"`
   557  		Specific map[string]interface{} `json:"specific"`
   558  	}
   559  	type estimateFeeRes struct {
   560  		FeePerTx   string `json:"feePerTx,omitempty"`
   561  		FeePerUnit string `json:"feePerUnit,omitempty"`
   562  		FeeLimit   string `json:"feeLimit,omitempty"`
   563  	}
   564  	var r estimateFeeReq
   565  	err := json.Unmarshal(params, &r)
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  	res := make([]estimateFeeRes, len(r.Blocks))
   570  	if s.chainParser.GetChainType() == bchain.ChainEthereumType {
   571  		gas, err := s.chain.EthereumTypeEstimateGas(r.Specific)
   572  		if err != nil {
   573  			return nil, err
   574  		}
   575  		sg := strconv.FormatUint(gas, 10)
   576  		for i, b := range r.Blocks {
   577  			fee, err := s.chain.EstimateSmartFee(b, true)
   578  			if err != nil {
   579  				return nil, err
   580  			}
   581  			res[i].FeePerUnit = fee.String()
   582  			res[i].FeeLimit = sg
   583  			fee.Mul(&fee, new(big.Int).SetUint64(gas))
   584  			res[i].FeePerTx = fee.String()
   585  		}
   586  	} else {
   587  		conservative := true
   588  		v, ok := r.Specific["conservative"]
   589  		if ok {
   590  			vc, ok := v.(bool)
   591  			if ok {
   592  				conservative = vc
   593  			}
   594  		}
   595  		txSize := 0
   596  		v, ok = r.Specific["txsize"]
   597  		if ok {
   598  			f, ok := v.(float64)
   599  			if ok {
   600  				txSize = int(f)
   601  			}
   602  		}
   603  		for i, b := range r.Blocks {
   604  			fee, err := s.chain.EstimateSmartFee(b, conservative)
   605  			if err != nil {
   606  				return nil, err
   607  			}
   608  			res[i].FeePerUnit = fee.String()
   609  			if txSize > 0 {
   610  				fee.Mul(&fee, big.NewInt(int64(txSize)))
   611  				fee.Add(&fee, big.NewInt(500))
   612  				fee.Div(&fee, big.NewInt(1000))
   613  				res[i].FeePerTx = fee.String()
   614  			}
   615  		}
   616  	}
   617  	return res, nil
   618  }
   619  
   620  func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction, err error) {
   621  	txid, err := s.chain.SendRawTransaction(tx)
   622  	if err != nil {
   623  		return res, err
   624  	}
   625  	res.Result = txid
   626  	return
   627  }
   628  
   629  type subscriptionResponse struct {
   630  	Subscribed bool `json:"subscribed"`
   631  }
   632  
   633  func (s *WebsocketServer) subscribeNewBlock(c *websocketChannel, req *websocketReq) (res interface{}, err error) {
   634  	s.newBlockSubscriptionsLock.Lock()
   635  	defer s.newBlockSubscriptionsLock.Unlock()
   636  	s.newBlockSubscriptions[c] = req.ID
   637  	return &subscriptionResponse{true}, nil
   638  }
   639  
   640  func (s *WebsocketServer) unsubscribeNewBlock(c *websocketChannel) (res interface{}, err error) {
   641  	s.newBlockSubscriptionsLock.Lock()
   642  	defer s.newBlockSubscriptionsLock.Unlock()
   643  	delete(s.newBlockSubscriptions, c)
   644  	return &subscriptionResponse{false}, nil
   645  }
   646  
   647  func (s *WebsocketServer) unmarshalAddresses(params []byte) ([]bchain.AddressDescriptor, error) {
   648  	r := struct {
   649  		Addresses []string `json:"addresses"`
   650  	}{}
   651  	err := json.Unmarshal(params, &r)
   652  	if err != nil {
   653  		return nil, err
   654  	}
   655  	rv := make([]bchain.AddressDescriptor, len(r.Addresses))
   656  	for i, a := range r.Addresses {
   657  		ad, err := s.chainParser.GetAddrDescFromAddress(a)
   658  		if err != nil {
   659  			return nil, err
   660  		}
   661  		rv[i] = ad
   662  	}
   663  	return rv, nil
   664  }
   665  
   666  func (s *WebsocketServer) subscribeAddresses(c *websocketChannel, addrDesc []bchain.AddressDescriptor, req *websocketReq) (res interface{}, err error) {
   667  	// unsubscribe all previous subscriptions
   668  	s.unsubscribeAddresses(c)
   669  	s.addressSubscriptionsLock.Lock()
   670  	defer s.addressSubscriptionsLock.Unlock()
   671  	for i := range addrDesc {
   672  		ads := string(addrDesc[i])
   673  		as, ok := s.addressSubscriptions[ads]
   674  		if !ok {
   675  			as = make(map[*websocketChannel]string)
   676  			s.addressSubscriptions[ads] = as
   677  		}
   678  		as[c] = req.ID
   679  	}
   680  	return &subscriptionResponse{true}, nil
   681  }
   682  
   683  // unsubscribeAddresses unsubscribes all address subscriptions by this channel
   684  func (s *WebsocketServer) unsubscribeAddresses(c *websocketChannel) (res interface{}, err error) {
   685  	s.addressSubscriptionsLock.Lock()
   686  	defer s.addressSubscriptionsLock.Unlock()
   687  	for _, sa := range s.addressSubscriptions {
   688  		for sc := range sa {
   689  			if sc == c {
   690  				delete(sa, c)
   691  			}
   692  		}
   693  	}
   694  	return &subscriptionResponse{false}, nil
   695  }
   696  
   697  // subscribeFiatRates subscribes all FiatRates subscriptions by this channel
   698  func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, currency string, req *websocketReq) (res interface{}, err error) {
   699  	// unsubscribe all previous subscriptions
   700  	s.unsubscribeFiatRates(c)
   701  	s.fiatRatesSubscriptionsLock.Lock()
   702  	defer s.fiatRatesSubscriptionsLock.Unlock()
   703  
   704  	if currency == "" {
   705  		currency = allFiatRates
   706  	}
   707  	as, ok := s.fiatRatesSubscriptions[currency]
   708  	if !ok {
   709  		as = make(map[*websocketChannel]string)
   710  		s.fiatRatesSubscriptions[currency] = as
   711  	}
   712  	as[c] = req.ID
   713  	return &subscriptionResponse{true}, nil
   714  }
   715  
   716  // unsubscribeFiatRates unsubscribes all FiatRates subscriptions by this channel
   717  func (s *WebsocketServer) unsubscribeFiatRates(c *websocketChannel) (res interface{}, err error) {
   718  	s.fiatRatesSubscriptionsLock.Lock()
   719  	defer s.fiatRatesSubscriptionsLock.Unlock()
   720  	for _, sa := range s.fiatRatesSubscriptions {
   721  		for sc := range sa {
   722  			if sc == c {
   723  				delete(sa, c)
   724  			}
   725  		}
   726  	}
   727  	return &subscriptionResponse{false}, nil
   728  }
   729  
   730  // OnNewBlock is a callback that broadcasts info about new block to subscribed clients
   731  func (s *WebsocketServer) OnNewBlock(hash string, height uint32) {
   732  	s.newBlockSubscriptionsLock.Lock()
   733  	defer s.newBlockSubscriptionsLock.Unlock()
   734  	data := struct {
   735  		Height uint32 `json:"height"`
   736  		Hash   string `json:"hash"`
   737  	}{
   738  		Height: height,
   739  		Hash:   hash,
   740  	}
   741  	for c, id := range s.newBlockSubscriptions {
   742  		if c.IsAlive() {
   743  			c.out <- &websocketRes{
   744  				ID:   id,
   745  				Data: &data,
   746  			}
   747  		}
   748  	}
   749  	glog.Info("broadcasting new block ", height, " ", hash, " to ", len(s.newBlockSubscriptions), " channels")
   750  }
   751  
   752  // OnNewTxAddr is a callback that broadcasts info about a tx affecting subscribed address
   753  func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDescriptor) {
   754  	// check if there is any subscription but release the lock immediately, GetTransactionFromBchainTx may take some time
   755  	s.addressSubscriptionsLock.Lock()
   756  	as, ok := s.addressSubscriptions[string(addrDesc)]
   757  	lenAs := len(as)
   758  	s.addressSubscriptionsLock.Unlock()
   759  	if ok && lenAs > 0 {
   760  		addr, _, err := s.chainParser.GetAddressesFromAddrDesc(addrDesc)
   761  		if err != nil {
   762  			glog.Error("GetAddressesFromAddrDesc error ", err, " for ", addrDesc)
   763  			return
   764  		}
   765  		if len(addr) == 1 {
   766  			atx, err := s.api.GetTransactionFromBchainTx(tx, 0, false, false)
   767  			if err != nil {
   768  				glog.Error("GetTransactionFromBchainTx error ", err, " for ", tx.Txid)
   769  				return
   770  			}
   771  			data := struct {
   772  				Address string  `json:"address"`
   773  				Tx      *api.Tx `json:"tx"`
   774  			}{
   775  				Address: addr[0],
   776  				Tx:      atx,
   777  			}
   778  			// get the list of subscriptions again, this time keep the lock
   779  			s.addressSubscriptionsLock.Lock()
   780  			defer s.addressSubscriptionsLock.Unlock()
   781  			as, ok = s.addressSubscriptions[string(addrDesc)]
   782  			if ok {
   783  				for c, id := range as {
   784  					if c.IsAlive() {
   785  						c.out <- &websocketRes{
   786  							ID:   id,
   787  							Data: &data,
   788  						}
   789  					}
   790  				}
   791  				glog.Info("broadcasting new tx ", tx.Txid, " for addr ", addr[0], " to ", len(as), " channels")
   792  			}
   793  		}
   794  	}
   795  }
   796  
   797  func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float64) {
   798  	s.fiatRatesSubscriptionsLock.Lock()
   799  	defer s.fiatRatesSubscriptionsLock.Unlock()
   800  	as, ok := s.fiatRatesSubscriptions[currency]
   801  	if ok && len(as) > 0 {
   802  		data := struct {
   803  			Rates interface{} `json:"rates"`
   804  		}{
   805  			Rates: rates,
   806  		}
   807  		// get the list of subscriptions again, this time keep the lock
   808  		as, ok = s.fiatRatesSubscriptions[currency]
   809  		if ok {
   810  			for c, id := range as {
   811  				if c.IsAlive() {
   812  					c.out <- &websocketRes{
   813  						ID:   id,
   814  						Data: &data,
   815  					}
   816  				}
   817  			}
   818  			glog.Info("broadcasting new rates for currency ", currency, " to ", len(as), " channels")
   819  		}
   820  	}
   821  }
   822  
   823  // OnNewFiatRatesTicker is a callback that broadcasts info about fiat rates affecting subscribed currency
   824  func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) {
   825  	for currency, rate := range ticker.Rates {
   826  		s.broadcastTicker(currency, map[string]float64{currency: rate})
   827  	}
   828  	s.broadcastTicker(allFiatRates, ticker.Rates)
   829  }
   830  
   831  func (s *WebsocketServer) getCurrentFiatRates(currencies []string) (interface{}, error) {
   832  	ret, err := s.api.GetCurrentFiatRates(currencies)
   833  	return ret, err
   834  }
   835  
   836  func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string) (interface{}, error) {
   837  	ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies)
   838  	return ret, err
   839  }
   840  
   841  func (s *WebsocketServer) getFiatRatesTickersList(timestamp int64) (interface{}, error) {
   842  	ret, err := s.api.GetFiatRatesTickersList(timestamp)
   843  	return ret, err
   844  }