github.com/dawnbass68/maddcash@v0.0.0-20201001105353-c91c12cb36e5/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 "txs":
   465  		opt = api.AccountDetailsTxHistory
   466  	default:
   467  		opt = api.AccountDetailsBasic
   468  	}
   469  	var tokensToReturn api.TokensToReturn
   470  	switch req.Tokens {
   471  	case "used":
   472  		tokensToReturn = api.TokensToReturnUsed
   473  	case "nonzero":
   474  		tokensToReturn = api.TokensToReturnNonzeroBalance
   475  	default:
   476  		tokensToReturn = api.TokensToReturnDerived
   477  	}
   478  	filter := api.AddressFilter{
   479  		FromHeight:     uint32(req.FromHeight),
   480  		ToHeight:       uint32(req.ToHeight),
   481  		Contract:       req.ContractFilter,
   482  		Vout:           api.AddressFilterVoutOff,
   483  		TokensToReturn: tokensToReturn,
   484  	}
   485  	if req.PageSize == 0 {
   486  		req.PageSize = txsOnPage
   487  	}
   488  	a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, req.Gap)
   489  	if err != nil {
   490  		return s.api.GetAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter)
   491  	}
   492  	return a, nil
   493  }
   494  
   495  func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error) {
   496  	utxo, err := s.api.GetXpubUtxo(descriptor, false, 0)
   497  	if err != nil {
   498  		return s.api.GetAddressUtxo(descriptor, false)
   499  	}
   500  	return utxo, nil
   501  }
   502  
   503  func (s *WebsocketServer) getTransaction(txid string) (interface{}, error) {
   504  	return s.api.GetTransaction(txid, false, false)
   505  }
   506  
   507  func (s *WebsocketServer) getTransactionSpecific(txid string) (interface{}, error) {
   508  	return s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid})
   509  }
   510  
   511  func (s *WebsocketServer) getInfo() (interface{}, error) {
   512  	vi := common.GetVersionInfo()
   513  	height, hash, err := s.db.GetBestBlock()
   514  	if err != nil {
   515  		return nil, err
   516  	}
   517  	type info struct {
   518  		Name       string `json:"name"`
   519  		Shortcut   string `json:"shortcut"`
   520  		Decimals   int    `json:"decimals"`
   521  		Version    string `json:"version"`
   522  		BestHeight int    `json:"bestHeight"`
   523  		BestHash   string `json:"bestHash"`
   524  		Block0Hash string `json:"block0Hash"`
   525  		Testnet    bool   `json:"testnet"`
   526  	}
   527  	return &info{
   528  		Name:       s.is.Coin,
   529  		Shortcut:   s.is.CoinShortcut,
   530  		Decimals:   s.chainParser.AmountDecimals(),
   531  		BestHeight: int(height),
   532  		BestHash:   hash,
   533  		Version:    vi.Version,
   534  		Block0Hash: s.block0hash,
   535  		Testnet:    s.chain.IsTestnet(),
   536  	}, nil
   537  }
   538  
   539  func (s *WebsocketServer) getBlockHash(height int) (interface{}, error) {
   540  	h, err := s.db.GetBlockHash(uint32(height))
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	type hash struct {
   545  		Hash string `json:"hash"`
   546  	}
   547  	return &hash{
   548  		Hash: h,
   549  	}, nil
   550  }
   551  
   552  func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (interface{}, error) {
   553  	type estimateFeeReq struct {
   554  		Blocks   []int                  `json:"blocks"`
   555  		Specific map[string]interface{} `json:"specific"`
   556  	}
   557  	type estimateFeeRes struct {
   558  		FeePerTx   string `json:"feePerTx,omitempty"`
   559  		FeePerUnit string `json:"feePerUnit,omitempty"`
   560  		FeeLimit   string `json:"feeLimit,omitempty"`
   561  	}
   562  	var r estimateFeeReq
   563  	err := json.Unmarshal(params, &r)
   564  	if err != nil {
   565  		return nil, err
   566  	}
   567  	res := make([]estimateFeeRes, len(r.Blocks))
   568  	if s.chainParser.GetChainType() == bchain.ChainEthereumType {
   569  		gas, err := s.chain.EthereumTypeEstimateGas(r.Specific)
   570  		if err != nil {
   571  			return nil, err
   572  		}
   573  		sg := strconv.FormatUint(gas, 10)
   574  		for i, b := range r.Blocks {
   575  			fee, err := s.chain.EstimateSmartFee(b, true)
   576  			if err != nil {
   577  				return nil, err
   578  			}
   579  			res[i].FeePerUnit = fee.String()
   580  			res[i].FeeLimit = sg
   581  			fee.Mul(&fee, new(big.Int).SetUint64(gas))
   582  			res[i].FeePerTx = fee.String()
   583  		}
   584  	} else {
   585  		conservative := true
   586  		v, ok := r.Specific["conservative"]
   587  		if ok {
   588  			vc, ok := v.(bool)
   589  			if ok {
   590  				conservative = vc
   591  			}
   592  		}
   593  		txSize := 0
   594  		v, ok = r.Specific["txsize"]
   595  		if ok {
   596  			f, ok := v.(float64)
   597  			if ok {
   598  				txSize = int(f)
   599  			}
   600  		}
   601  		for i, b := range r.Blocks {
   602  			fee, err := s.chain.EstimateSmartFee(b, conservative)
   603  			if err != nil {
   604  				return nil, err
   605  			}
   606  			res[i].FeePerUnit = fee.String()
   607  			if txSize > 0 {
   608  				fee.Mul(&fee, big.NewInt(int64(txSize)))
   609  				fee.Add(&fee, big.NewInt(500))
   610  				fee.Div(&fee, big.NewInt(1000))
   611  				res[i].FeePerTx = fee.String()
   612  			}
   613  		}
   614  	}
   615  	return res, nil
   616  }
   617  
   618  func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction, err error) {
   619  	txid, err := s.chain.SendRawTransaction(tx)
   620  	if err != nil {
   621  		return res, err
   622  	}
   623  	res.Result = txid
   624  	return
   625  }
   626  
   627  type subscriptionResponse struct {
   628  	Subscribed bool `json:"subscribed"`
   629  }
   630  
   631  func (s *WebsocketServer) subscribeNewBlock(c *websocketChannel, req *websocketReq) (res interface{}, err error) {
   632  	s.newBlockSubscriptionsLock.Lock()
   633  	defer s.newBlockSubscriptionsLock.Unlock()
   634  	s.newBlockSubscriptions[c] = req.ID
   635  	return &subscriptionResponse{true}, nil
   636  }
   637  
   638  func (s *WebsocketServer) unsubscribeNewBlock(c *websocketChannel) (res interface{}, err error) {
   639  	s.newBlockSubscriptionsLock.Lock()
   640  	defer s.newBlockSubscriptionsLock.Unlock()
   641  	delete(s.newBlockSubscriptions, c)
   642  	return &subscriptionResponse{false}, nil
   643  }
   644  
   645  func (s *WebsocketServer) unmarshalAddresses(params []byte) ([]bchain.AddressDescriptor, error) {
   646  	r := struct {
   647  		Addresses []string `json:"addresses"`
   648  	}{}
   649  	err := json.Unmarshal(params, &r)
   650  	if err != nil {
   651  		return nil, err
   652  	}
   653  	rv := make([]bchain.AddressDescriptor, len(r.Addresses))
   654  	for i, a := range r.Addresses {
   655  		ad, err := s.chainParser.GetAddrDescFromAddress(a)
   656  		if err != nil {
   657  			return nil, err
   658  		}
   659  		rv[i] = ad
   660  	}
   661  	return rv, nil
   662  }
   663  
   664  func (s *WebsocketServer) subscribeAddresses(c *websocketChannel, addrDesc []bchain.AddressDescriptor, req *websocketReq) (res interface{}, err error) {
   665  	// unsubscribe all previous subscriptions
   666  	s.unsubscribeAddresses(c)
   667  	s.addressSubscriptionsLock.Lock()
   668  	defer s.addressSubscriptionsLock.Unlock()
   669  	for i := range addrDesc {
   670  		ads := string(addrDesc[i])
   671  		as, ok := s.addressSubscriptions[ads]
   672  		if !ok {
   673  			as = make(map[*websocketChannel]string)
   674  			s.addressSubscriptions[ads] = as
   675  		}
   676  		as[c] = req.ID
   677  	}
   678  	return &subscriptionResponse{true}, nil
   679  }
   680  
   681  // unsubscribeAddresses unsubscribes all address subscriptions by this channel
   682  func (s *WebsocketServer) unsubscribeAddresses(c *websocketChannel) (res interface{}, err error) {
   683  	s.addressSubscriptionsLock.Lock()
   684  	defer s.addressSubscriptionsLock.Unlock()
   685  	for _, sa := range s.addressSubscriptions {
   686  		for sc := range sa {
   687  			if sc == c {
   688  				delete(sa, c)
   689  			}
   690  		}
   691  	}
   692  	return &subscriptionResponse{false}, nil
   693  }
   694  
   695  // subscribeFiatRates subscribes all FiatRates subscriptions by this channel
   696  func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, currency string, req *websocketReq) (res interface{}, err error) {
   697  	// unsubscribe all previous subscriptions
   698  	s.unsubscribeFiatRates(c)
   699  	s.fiatRatesSubscriptionsLock.Lock()
   700  	defer s.fiatRatesSubscriptionsLock.Unlock()
   701  
   702  	if currency == "" {
   703  		currency = allFiatRates
   704  	}
   705  	as, ok := s.fiatRatesSubscriptions[currency]
   706  	if !ok {
   707  		as = make(map[*websocketChannel]string)
   708  		s.fiatRatesSubscriptions[currency] = as
   709  	}
   710  	as[c] = req.ID
   711  	return &subscriptionResponse{true}, nil
   712  }
   713  
   714  // unsubscribeFiatRates unsubscribes all FiatRates subscriptions by this channel
   715  func (s *WebsocketServer) unsubscribeFiatRates(c *websocketChannel) (res interface{}, err error) {
   716  	s.fiatRatesSubscriptionsLock.Lock()
   717  	defer s.fiatRatesSubscriptionsLock.Unlock()
   718  	for _, sa := range s.fiatRatesSubscriptions {
   719  		for sc := range sa {
   720  			if sc == c {
   721  				delete(sa, c)
   722  			}
   723  		}
   724  	}
   725  	return &subscriptionResponse{false}, nil
   726  }
   727  
   728  // OnNewBlock is a callback that broadcasts info about new block to subscribed clients
   729  func (s *WebsocketServer) OnNewBlock(hash string, height uint32) {
   730  	s.newBlockSubscriptionsLock.Lock()
   731  	defer s.newBlockSubscriptionsLock.Unlock()
   732  	data := struct {
   733  		Height uint32 `json:"height"`
   734  		Hash   string `json:"hash"`
   735  	}{
   736  		Height: height,
   737  		Hash:   hash,
   738  	}
   739  	for c, id := range s.newBlockSubscriptions {
   740  		if c.IsAlive() {
   741  			c.out <- &websocketRes{
   742  				ID:   id,
   743  				Data: &data,
   744  			}
   745  		}
   746  	}
   747  	glog.Info("broadcasting new block ", height, " ", hash, " to ", len(s.newBlockSubscriptions), " channels")
   748  }
   749  
   750  // OnNewTxAddr is a callback that broadcasts info about a tx affecting subscribed address
   751  func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDescriptor) {
   752  	// check if there is any subscription but release the lock immediately, GetTransactionFromBchainTx may take some time
   753  	s.addressSubscriptionsLock.Lock()
   754  	as, ok := s.addressSubscriptions[string(addrDesc)]
   755  	lenAs := len(as)
   756  	s.addressSubscriptionsLock.Unlock()
   757  	if ok && lenAs > 0 {
   758  		addr, _, err := s.chainParser.GetAddressesFromAddrDesc(addrDesc)
   759  		if err != nil {
   760  			glog.Error("GetAddressesFromAddrDesc error ", err, " for ", addrDesc)
   761  			return
   762  		}
   763  		if len(addr) == 1 {
   764  			atx, err := s.api.GetTransactionFromBchainTx(tx, 0, false, false)
   765  			if err != nil {
   766  				glog.Error("GetTransactionFromBchainTx error ", err, " for ", tx.Txid)
   767  				return
   768  			}
   769  			data := struct {
   770  				Address string  `json:"address"`
   771  				Tx      *api.Tx `json:"tx"`
   772  			}{
   773  				Address: addr[0],
   774  				Tx:      atx,
   775  			}
   776  			// get the list of subscriptions again, this time keep the lock
   777  			s.addressSubscriptionsLock.Lock()
   778  			defer s.addressSubscriptionsLock.Unlock()
   779  			as, ok = s.addressSubscriptions[string(addrDesc)]
   780  			if ok {
   781  				for c, id := range as {
   782  					if c.IsAlive() {
   783  						c.out <- &websocketRes{
   784  							ID:   id,
   785  							Data: &data,
   786  						}
   787  					}
   788  				}
   789  				glog.Info("broadcasting new tx ", tx.Txid, " for addr ", addr[0], " to ", len(as), " channels")
   790  			}
   791  		}
   792  	}
   793  }
   794  
   795  func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float64) {
   796  	s.fiatRatesSubscriptionsLock.Lock()
   797  	defer s.fiatRatesSubscriptionsLock.Unlock()
   798  	as, ok := s.fiatRatesSubscriptions[currency]
   799  	if ok && len(as) > 0 {
   800  		data := struct {
   801  			Rates interface{} `json:"rates"`
   802  		}{
   803  			Rates: rates,
   804  		}
   805  		// get the list of subscriptions again, this time keep the lock
   806  		as, ok = s.fiatRatesSubscriptions[currency]
   807  		if ok {
   808  			for c, id := range as {
   809  				if c.IsAlive() {
   810  					c.out <- &websocketRes{
   811  						ID:   id,
   812  						Data: &data,
   813  					}
   814  				}
   815  			}
   816  			glog.Info("broadcasting new rates for currency ", currency, " to ", len(as), " channels")
   817  		}
   818  	}
   819  }
   820  
   821  // OnNewFiatRatesTicker is a callback that broadcasts info about fiat rates affecting subscribed currency
   822  func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) {
   823  	for currency, rate := range ticker.Rates {
   824  		s.broadcastTicker(currency, map[string]float64{currency: rate})
   825  	}
   826  	s.broadcastTicker(allFiatRates, ticker.Rates)
   827  }
   828  
   829  func (s *WebsocketServer) getCurrentFiatRates(currencies []string) (interface{}, error) {
   830  	ret, err := s.api.GetCurrentFiatRates(currencies)
   831  	return ret, err
   832  }
   833  
   834  func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string) (interface{}, error) {
   835  	ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies)
   836  	return ret, err
   837  }
   838  
   839  func (s *WebsocketServer) getFiatRatesTickersList(timestamp int64) (interface{}, error) {
   840  	ret, err := s.api.GetFiatRatesTickersList(timestamp)
   841  	return ret, err
   842  }