github.com/jackcoble/blockbook@v0.3.2/api/worker.go (about)

     1  package api
     2  
     3  import (
     4  	"blockbook/bchain"
     5  	"blockbook/bchain/coins/eth"
     6  	"blockbook/common"
     7  	"blockbook/db"
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"math"
    12  	"math/big"
    13  	"os"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/golang/glog"
    20  	"github.com/juju/errors"
    21  )
    22  
    23  // Worker is handle to api worker
    24  type Worker struct {
    25  	db          *db.RocksDB
    26  	txCache     *db.TxCache
    27  	chain       bchain.BlockChain
    28  	chainParser bchain.BlockChainParser
    29  	chainType   bchain.ChainType
    30  	mempool     bchain.Mempool
    31  	is          *common.InternalState
    32  }
    33  
    34  // NewWorker creates new api worker
    35  func NewWorker(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, is *common.InternalState) (*Worker, error) {
    36  	w := &Worker{
    37  		db:          db,
    38  		txCache:     txCache,
    39  		chain:       chain,
    40  		chainParser: chain.GetChainParser(),
    41  		chainType:   chain.GetChainParser().GetChainType(),
    42  		mempool:     mempool,
    43  		is:          is,
    44  	}
    45  	return w, nil
    46  }
    47  
    48  func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescriptor, []string, bool, error) {
    49  	addrDesc, err := w.chainParser.GetAddrDescFromVout(vout)
    50  	if err != nil {
    51  		return nil, nil, false, err
    52  	}
    53  	a, s, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
    54  	return addrDesc, a, s, err
    55  }
    56  
    57  // setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
    58  // there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
    59  func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
    60  	err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, maxUint32, func(t string, height uint32, indexes []int32) error {
    61  		for _, index := range indexes {
    62  			// take only inputs
    63  			if index < 0 {
    64  				index = ^index
    65  				tsp, err := w.db.GetTxAddresses(t)
    66  				if err != nil {
    67  					return err
    68  				} else if tsp == nil {
    69  					glog.Warning("DB inconsistency:  tx ", t, ": not found in txAddresses")
    70  				} else if len(tsp.Inputs) > int(index) {
    71  					if tsp.Inputs[index].ValueSat.Cmp((*big.Int)(vout.ValueSat)) == 0 {
    72  						spentTx, spentHeight, err := w.txCache.GetTransaction(t)
    73  						if err != nil {
    74  							glog.Warning("Tx ", t, ": not found")
    75  						} else {
    76  							if len(spentTx.Vin) > int(index) {
    77  								if spentTx.Vin[index].Txid == txid {
    78  									vout.SpentTxID = t
    79  									vout.SpentHeight = int(spentHeight)
    80  									vout.SpentIndex = int(index)
    81  									return &db.StopIteration{}
    82  								}
    83  							}
    84  						}
    85  					}
    86  				}
    87  			}
    88  		}
    89  		return nil
    90  	})
    91  	return err
    92  }
    93  
    94  // GetSpendingTxid returns transaction id of transaction that spent given output
    95  func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
    96  	start := time.Now()
    97  	tx, err := w.GetTransaction(txid, false, false)
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  	if n >= len(tx.Vout) || n < 0 {
   102  		return "", NewAPIError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false)
   103  	}
   104  	err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight))
   105  	if err != nil {
   106  		return "", err
   107  	}
   108  	glog.Info("GetSpendingTxid ", txid, " ", n, " finished in ", time.Since(start))
   109  	return tx.Vout[n].SpentTxID, nil
   110  }
   111  
   112  // GetTransaction reads transaction data from txid
   113  func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool) (*Tx, error) {
   114  	bchainTx, height, err := w.txCache.GetTransaction(txid)
   115  	if err != nil {
   116  		if err == bchain.ErrTxNotFound {
   117  			return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found", txid), true)
   118  		}
   119  		return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found (%v)", txid, err), true)
   120  	}
   121  	return w.GetTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON)
   122  }
   123  
   124  // GetTransactionFromBchainTx reads transaction data from txid
   125  func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spendingTxs bool, specificJSON bool) (*Tx, error) {
   126  	var err error
   127  	var ta *db.TxAddresses
   128  	var tokens []TokenTransfer
   129  	var ethSpecific *EthereumSpecific
   130  	var blockhash string
   131  	if bchainTx.Confirmations > 0 {
   132  		if w.chainType == bchain.ChainBitcoinType {
   133  			ta, err = w.db.GetTxAddresses(bchainTx.Txid)
   134  			if err != nil {
   135  				return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainTx.Txid)
   136  			}
   137  		}
   138  		blockhash, err = w.db.GetBlockHash(uint32(height))
   139  		if err != nil {
   140  			return nil, errors.Annotatef(err, "GetBlockHash %v", height)
   141  		}
   142  	}
   143  	var valInSat, valOutSat, feesSat big.Int
   144  	var pValInSat *big.Int
   145  	vins := make([]Vin, len(bchainTx.Vin))
   146  	rbf := false
   147  	for i := range bchainTx.Vin {
   148  		bchainVin := &bchainTx.Vin[i]
   149  		vin := &vins[i]
   150  		vin.Txid = bchainVin.Txid
   151  		vin.N = i
   152  		vin.Vout = bchainVin.Vout
   153  		vin.Sequence = int64(bchainVin.Sequence)
   154  		// detect explicit Replace-by-Fee transactions as defined by BIP125
   155  		if bchainTx.Confirmations == 0 && bchainVin.Sequence < 0xffffffff-1 {
   156  			rbf = true
   157  		}
   158  		vin.Hex = bchainVin.ScriptSig.Hex
   159  		vin.Coinbase = bchainVin.Coinbase
   160  		if w.chainType == bchain.ChainBitcoinType {
   161  			//  bchainVin.Txid=="" is coinbase transaction
   162  			if bchainVin.Txid != "" {
   163  				// load spending addresses from TxAddresses
   164  				tas, err := w.db.GetTxAddresses(bchainVin.Txid)
   165  				if err != nil {
   166  					return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid)
   167  				}
   168  				if tas == nil {
   169  					// try to load from backend
   170  					otx, _, err := w.txCache.GetTransaction(bchainVin.Txid)
   171  					if err != nil {
   172  						if err == bchain.ErrTxNotFound {
   173  							// try to get AddrDesc using coin specific handling and continue processing the tx
   174  							vin.AddrDesc = w.chainParser.GetAddrDescForUnknownInput(bchainTx, i)
   175  							vin.Addresses, vin.IsAddress, err = w.chainParser.GetAddressesFromAddrDesc(vin.AddrDesc)
   176  							if err != nil {
   177  								glog.Warning("GetAddressesFromAddrDesc tx ", bchainVin.Txid, ", addrDesc ", vin.AddrDesc, ": ", err)
   178  							}
   179  							continue
   180  						}
   181  						return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid)
   182  					}
   183  					// mempool transactions are not in TxAddresses but confirmed should be there, log a problem
   184  					// ignore when Confirmations==1, it may be just a timing problem
   185  					if bchainTx.Confirmations > 1 {
   186  						glog.Warning("DB inconsistency:  tx ", bchainVin.Txid, ": not found in txAddresses, confirmations ", bchainTx.Confirmations)
   187  					}
   188  					if len(otx.Vout) > int(vin.Vout) {
   189  						vout := &otx.Vout[vin.Vout]
   190  						vin.ValueSat = (*Amount)(&vout.ValueSat)
   191  						vin.AddrDesc, vin.Addresses, vin.IsAddress, err = w.getAddressesFromVout(vout)
   192  						if err != nil {
   193  							glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout)
   194  						}
   195  					}
   196  				} else {
   197  					if len(tas.Outputs) > int(vin.Vout) {
   198  						output := &tas.Outputs[vin.Vout]
   199  						vin.ValueSat = (*Amount)(&output.ValueSat)
   200  						vin.AddrDesc = output.AddrDesc
   201  						vin.Addresses, vin.IsAddress, err = output.Addresses(w.chainParser)
   202  						if err != nil {
   203  							glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i)
   204  						}
   205  					}
   206  				}
   207  				if vin.ValueSat != nil {
   208  					valInSat.Add(&valInSat, (*big.Int)(vin.ValueSat))
   209  				}
   210  			}
   211  		} else if w.chainType == bchain.ChainEthereumType {
   212  			if len(bchainVin.Addresses) > 0 {
   213  				vin.AddrDesc, err = w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0])
   214  				if err != nil {
   215  					glog.Errorf("GetAddrDescFromAddress error %v, tx %v, bchainVin %v", err, bchainTx.Txid, bchainVin)
   216  				}
   217  				vin.Addresses = bchainVin.Addresses
   218  				vin.IsAddress = true
   219  			}
   220  		}
   221  	}
   222  	vouts := make([]Vout, len(bchainTx.Vout))
   223  	for i := range bchainTx.Vout {
   224  		bchainVout := &bchainTx.Vout[i]
   225  		vout := &vouts[i]
   226  		vout.N = i
   227  		vout.ValueSat = (*Amount)(&bchainVout.ValueSat)
   228  		valOutSat.Add(&valOutSat, &bchainVout.ValueSat)
   229  		vout.Hex = bchainVout.ScriptPubKey.Hex
   230  		vout.AddrDesc, vout.Addresses, vout.IsAddress, err = w.getAddressesFromVout(bchainVout)
   231  		if err != nil {
   232  			glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, bchainTx.Txid, bchainVout.N)
   233  		}
   234  		if ta != nil {
   235  			vout.Spent = ta.Outputs[i].Spent
   236  			if spendingTxs && vout.Spent {
   237  				err = w.setSpendingTxToVout(vout, bchainTx.Txid, uint32(height))
   238  				if err != nil {
   239  					glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.AddrDesc, vout.N)
   240  				}
   241  			}
   242  		}
   243  	}
   244  	if w.chainType == bchain.ChainBitcoinType {
   245  		// for coinbase transactions valIn is 0
   246  		feesSat.Sub(&valInSat, &valOutSat)
   247  		if feesSat.Sign() == -1 {
   248  			feesSat.SetUint64(0)
   249  		}
   250  		pValInSat = &valInSat
   251  	} else if w.chainType == bchain.ChainEthereumType {
   252  		ets, err := w.chainParser.EthereumTypeGetErc20FromTx(bchainTx)
   253  		if err != nil {
   254  			glog.Errorf("GetErc20FromTx error %v, %v", err, bchainTx)
   255  		}
   256  		tokens = make([]TokenTransfer, len(ets))
   257  		for i := range ets {
   258  			e := &ets[i]
   259  			cd, err := w.chainParser.GetAddrDescFromAddress(e.Contract)
   260  			if err != nil {
   261  				glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, e.Contract)
   262  				continue
   263  			}
   264  			erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd)
   265  			if err != nil {
   266  				glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, e.Contract)
   267  			}
   268  			if erc20c == nil {
   269  				erc20c = &bchain.Erc20Contract{Name: e.Contract}
   270  			}
   271  			tokens[i] = TokenTransfer{
   272  				Type:     ERC20TokenType,
   273  				Token:    e.Contract,
   274  				From:     e.From,
   275  				To:       e.To,
   276  				Decimals: erc20c.Decimals,
   277  				Value:    (*Amount)(&e.Tokens),
   278  				Name:     erc20c.Name,
   279  				Symbol:   erc20c.Symbol,
   280  			}
   281  		}
   282  		ethTxData := eth.GetEthereumTxData(bchainTx)
   283  		// mempool txs do not have fees yet
   284  		if ethTxData.GasUsed != nil {
   285  			feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed)
   286  		}
   287  		if len(bchainTx.Vout) > 0 {
   288  			valOutSat = bchainTx.Vout[0].ValueSat
   289  		}
   290  		ethSpecific = &EthereumSpecific{
   291  			GasLimit: ethTxData.GasLimit,
   292  			GasPrice: (*Amount)(ethTxData.GasPrice),
   293  			GasUsed:  ethTxData.GasUsed,
   294  			Nonce:    ethTxData.Nonce,
   295  			Status:   ethTxData.Status,
   296  		}
   297  	}
   298  	// for now do not return size, we would have to compute vsize of segwit transactions
   299  	// size:=len(bchainTx.Hex) / 2
   300  	var sj json.RawMessage
   301  	if specificJSON {
   302  		sj, err = w.chain.GetTransactionSpecific(bchainTx)
   303  		if err != nil {
   304  			return nil, err
   305  		}
   306  	}
   307  	// for mempool transaction get first seen time
   308  	if bchainTx.Confirmations == 0 {
   309  		bchainTx.Blocktime = int64(w.mempool.GetTransactionTime(bchainTx.Txid))
   310  	}
   311  	r := &Tx{
   312  		Blockhash:        blockhash,
   313  		Blockheight:      height,
   314  		Blocktime:        bchainTx.Blocktime,
   315  		Confirmations:    bchainTx.Confirmations,
   316  		FeesSat:          (*Amount)(&feesSat),
   317  		Locktime:         bchainTx.LockTime,
   318  		Txid:             bchainTx.Txid,
   319  		ValueInSat:       (*Amount)(pValInSat),
   320  		ValueOutSat:      (*Amount)(&valOutSat),
   321  		Version:          bchainTx.Version,
   322  		Hex:              bchainTx.Hex,
   323  		Rbf:              rbf,
   324  		Vin:              vins,
   325  		Vout:             vouts,
   326  		CoinSpecificData: bchainTx.CoinSpecificData,
   327  		CoinSpecificJSON: sj,
   328  		TokenTransfers:   tokens,
   329  		EthereumSpecific: ethSpecific,
   330  	}
   331  	return r, nil
   332  }
   333  
   334  func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter *AddressFilter, maxResults int) ([]string, error) {
   335  	var err error
   336  	txids := make([]string, 0, 4)
   337  	var callback db.GetTransactionsCallback
   338  	if filter.Vout == AddressFilterVoutOff {
   339  		callback = func(txid string, height uint32, indexes []int32) error {
   340  			txids = append(txids, txid)
   341  			if len(txids) >= maxResults {
   342  				return &db.StopIteration{}
   343  			}
   344  			return nil
   345  		}
   346  	} else {
   347  		callback = func(txid string, height uint32, indexes []int32) error {
   348  			for _, index := range indexes {
   349  				vout := index
   350  				if vout < 0 {
   351  					vout = ^vout
   352  				}
   353  				if (filter.Vout == AddressFilterVoutInputs && index < 0) ||
   354  					(filter.Vout == AddressFilterVoutOutputs && index >= 0) ||
   355  					(vout == int32(filter.Vout)) {
   356  					txids = append(txids, txid)
   357  					if len(txids) >= maxResults {
   358  						return &db.StopIteration{}
   359  					}
   360  					break
   361  				}
   362  			}
   363  			return nil
   364  		}
   365  	}
   366  	if mempool {
   367  		uniqueTxs := make(map[string]struct{})
   368  		o, err := w.mempool.GetAddrDescTransactions(addrDesc)
   369  		if err != nil {
   370  			return nil, err
   371  		}
   372  		for _, m := range o {
   373  			if _, found := uniqueTxs[m.Txid]; !found {
   374  				l := len(txids)
   375  				callback(m.Txid, 0, []int32{m.Vout})
   376  				if len(txids) > l {
   377  					uniqueTxs[m.Txid] = struct{}{}
   378  				}
   379  			}
   380  		}
   381  	} else {
   382  		to := filter.ToHeight
   383  		if to == 0 {
   384  			to = maxUint32
   385  		}
   386  		err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback)
   387  		if err != nil {
   388  			return nil, err
   389  		}
   390  	}
   391  	return txids, nil
   392  }
   393  
   394  func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int {
   395  	var val big.Int
   396  	for _, vout := range t.Vout {
   397  		if bytes.Equal(vout.AddrDesc, addrDesc) && vout.ValueSat != nil {
   398  			val.Add(&val, (*big.Int)(vout.ValueSat))
   399  		}
   400  	}
   401  	return &val
   402  }
   403  
   404  func (t *Tx) getAddrVinValue(addrDesc bchain.AddressDescriptor) *big.Int {
   405  	var val big.Int
   406  	for _, vin := range t.Vin {
   407  		if bytes.Equal(vin.AddrDesc, addrDesc) && vin.ValueSat != nil {
   408  			val.Add(&val, (*big.Int)(vin.ValueSat))
   409  		}
   410  	}
   411  	return &val
   412  }
   413  
   414  // GetUniqueTxids removes duplicate transactions
   415  func GetUniqueTxids(txids []string) []string {
   416  	ut := make([]string, len(txids))
   417  	txidsMap := make(map[string]struct{})
   418  	i := 0
   419  	for _, txid := range txids {
   420  		_, e := txidsMap[txid]
   421  		if !e {
   422  			ut[i] = txid
   423  			i++
   424  			txidsMap[txid] = struct{}{}
   425  		}
   426  	}
   427  	return ut[0:i]
   428  }
   429  
   430  func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32) *Tx {
   431  	var err error
   432  	var valInSat, valOutSat, feesSat big.Int
   433  	vins := make([]Vin, len(ta.Inputs))
   434  	for i := range ta.Inputs {
   435  		tai := &ta.Inputs[i]
   436  		vin := &vins[i]
   437  		vin.N = i
   438  		vin.ValueSat = (*Amount)(&tai.ValueSat)
   439  		valInSat.Add(&valInSat, &tai.ValueSat)
   440  		vin.Addresses, vin.IsAddress, err = tai.Addresses(w.chainParser)
   441  		if err != nil {
   442  			glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai)
   443  		}
   444  	}
   445  	vouts := make([]Vout, len(ta.Outputs))
   446  	for i := range ta.Outputs {
   447  		tao := &ta.Outputs[i]
   448  		vout := &vouts[i]
   449  		vout.N = i
   450  		vout.ValueSat = (*Amount)(&tao.ValueSat)
   451  		valOutSat.Add(&valOutSat, &tao.ValueSat)
   452  		vout.Addresses, vout.IsAddress, err = tao.Addresses(w.chainParser)
   453  		if err != nil {
   454  			glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao)
   455  		}
   456  		vout.Spent = tao.Spent
   457  	}
   458  	// for coinbase transactions valIn is 0
   459  	feesSat.Sub(&valInSat, &valOutSat)
   460  	if feesSat.Sign() == -1 {
   461  		feesSat.SetUint64(0)
   462  	}
   463  	r := &Tx{
   464  		Blockhash:     bi.Hash,
   465  		Blockheight:   int(ta.Height),
   466  		Blocktime:     bi.Time,
   467  		Confirmations: bestheight - ta.Height + 1,
   468  		FeesSat:       (*Amount)(&feesSat),
   469  		Txid:          txid,
   470  		ValueInSat:    (*Amount)(&valInSat),
   471  		ValueOutSat:   (*Amount)(&valOutSat),
   472  		Vin:           vins,
   473  		Vout:          vouts,
   474  	}
   475  	return r
   476  }
   477  
   478  func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
   479  	from := page * itemsOnPage
   480  	totalPages := (count - 1) / itemsOnPage
   481  	if totalPages < 0 {
   482  		totalPages = 0
   483  	}
   484  	if from >= count {
   485  		page = totalPages
   486  	}
   487  	from = page * itemsOnPage
   488  	to := (page + 1) * itemsOnPage
   489  	if to > count {
   490  		to = count
   491  	}
   492  	return Paging{
   493  		ItemsOnPage: itemsOnPage,
   494  		Page:        page + 1,
   495  		TotalPages:  totalPages + 1,
   496  	}, from, to, page
   497  }
   498  
   499  func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) {
   500  	var (
   501  		ba             *db.AddrBalance
   502  		tokens         []Token
   503  		ci             *bchain.Erc20Contract
   504  		n              uint64
   505  		nonContractTxs int
   506  	)
   507  	// unknown number of results for paging
   508  	totalResults := -1
   509  	ca, err := w.db.GetAddrDescContracts(addrDesc)
   510  	if err != nil {
   511  		return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
   512  	}
   513  	b, err := w.chain.EthereumTypeGetBalance(addrDesc)
   514  	if err != nil {
   515  		return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc)
   516  	}
   517  	if ca != nil {
   518  		ba = &db.AddrBalance{
   519  			Txs: uint32(ca.TotalTxs),
   520  		}
   521  		if b != nil {
   522  			ba.BalanceSat = *b
   523  		}
   524  		n, err = w.chain.EthereumTypeGetNonce(addrDesc)
   525  		if err != nil {
   526  			return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc)
   527  		}
   528  		var filterDesc bchain.AddressDescriptor
   529  		if filter.Contract != "" {
   530  			filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract)
   531  			if err != nil {
   532  				return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
   533  			}
   534  		}
   535  		if details > AccountDetailsBasic {
   536  			tokens = make([]Token, len(ca.Contracts))
   537  			var j int
   538  			for i, c := range ca.Contracts {
   539  				if len(filterDesc) > 0 {
   540  					if !bytes.Equal(filterDesc, c.Contract) {
   541  						continue
   542  					}
   543  					// filter only transactions of this contract
   544  					filter.Vout = i + 1
   545  				}
   546  				validContract := true
   547  				ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
   548  				if err != nil {
   549  					return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
   550  				}
   551  				if ci == nil {
   552  					ci = &bchain.Erc20Contract{}
   553  					addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
   554  					if len(addresses) > 0 {
   555  						ci.Contract = addresses[0]
   556  						ci.Name = addresses[0]
   557  					}
   558  					validContract = false
   559  				}
   560  				// do not read contract balances etc in case of Basic option
   561  				if details >= AccountDetailsTokenBalances && validContract {
   562  					b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
   563  					if err != nil {
   564  						// return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
   565  						glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err)
   566  					}
   567  				} else {
   568  					b = nil
   569  				}
   570  				tokens[j] = Token{
   571  					Type:          ERC20TokenType,
   572  					BalanceSat:    (*Amount)(b),
   573  					Contract:      ci.Contract,
   574  					Name:          ci.Name,
   575  					Symbol:        ci.Symbol,
   576  					Transfers:     int(c.Txs),
   577  					Decimals:      ci.Decimals,
   578  					ContractIndex: strconv.Itoa(i + 1),
   579  				}
   580  				j++
   581  			}
   582  			tokens = tokens[:j]
   583  		}
   584  		ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
   585  		if err != nil {
   586  			return nil, nil, nil, 0, 0, 0, err
   587  		}
   588  		if filter.FromHeight == 0 && filter.ToHeight == 0 {
   589  			// compute total results for paging
   590  			if filter.Vout == AddressFilterVoutOff {
   591  				totalResults = int(ca.TotalTxs)
   592  			} else if filter.Vout == 0 {
   593  				totalResults = int(ca.NonContractTxs)
   594  			} else if filter.Vout > 0 && filter.Vout-1 < len(ca.Contracts) {
   595  				totalResults = int(ca.Contracts[filter.Vout-1].Txs)
   596  			}
   597  		}
   598  		nonContractTxs = int(ca.NonContractTxs)
   599  	} else {
   600  		// addresses without any normal transactions can have internal transactions and therefore balance
   601  		if b != nil {
   602  			ba = &db.AddrBalance{
   603  				BalanceSat: *b,
   604  			}
   605  		}
   606  	}
   607  	return ba, tokens, ci, n, nonContractTxs, totalResults, nil
   608  }
   609  
   610  func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails, blockInfo *db.BlockInfo) (*Tx, error) {
   611  	var tx *Tx
   612  	var err error
   613  	// only ChainBitcoinType supports TxHistoryLight
   614  	if option == AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType {
   615  		ta, err := w.db.GetTxAddresses(txid)
   616  		if err != nil {
   617  			return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
   618  		}
   619  		if ta == nil {
   620  			glog.Warning("DB inconsistency:  tx ", txid, ": not found in txAddresses")
   621  			// as fallback, get tx from backend
   622  			tx, err = w.GetTransaction(txid, false, true)
   623  			if err != nil {
   624  				return nil, errors.Annotatef(err, "GetTransaction %v", txid)
   625  			}
   626  		} else {
   627  			if blockInfo == nil {
   628  				blockInfo, err = w.db.GetBlockInfo(ta.Height)
   629  				if err != nil {
   630  					return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
   631  				}
   632  				if blockInfo == nil {
   633  					glog.Warning("DB inconsistency:  block height ", ta.Height, ": not found in db")
   634  					// provide empty BlockInfo to return the rest of tx data
   635  					blockInfo = &db.BlockInfo{}
   636  				}
   637  			}
   638  			tx = w.txFromTxAddress(txid, ta, blockInfo, bestheight)
   639  		}
   640  	} else {
   641  		tx, err = w.GetTransaction(txid, false, true)
   642  		if err != nil {
   643  			return nil, errors.Annotatef(err, "GetTransaction %v", txid)
   644  		}
   645  	}
   646  	return tx, nil
   647  }
   648  
   649  func (w *Worker) getAddrDescAndNormalizeAddress(address string) (bchain.AddressDescriptor, string, error) {
   650  	addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
   651  	if err != nil {
   652  		var errAd error
   653  		// try if the address is not address descriptor converted to string
   654  		addrDesc, errAd = bchain.AddressDescriptorFromString(address)
   655  		if errAd != nil {
   656  			return nil, "", NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
   657  		}
   658  	}
   659  	// convert the address to the format defined by the parser
   660  	addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
   661  	if err != nil {
   662  		glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc)
   663  	}
   664  	if len(addresses) == 1 {
   665  		address = addresses[0]
   666  	}
   667  	return addrDesc, address, nil
   668  }
   669  
   670  // GetAddress computes address value and gets transactions for given address
   671  func (w *Worker) GetAddress(address string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter) (*Address, error) {
   672  	start := time.Now()
   673  	page--
   674  	if page < 0 {
   675  		page = 0
   676  	}
   677  	var (
   678  		ba                       *db.AddrBalance
   679  		tokens                   []Token
   680  		erc20c                   *bchain.Erc20Contract
   681  		txm                      []string
   682  		txs                      []*Tx
   683  		txids                    []string
   684  		pg                       Paging
   685  		uBalSat                  big.Int
   686  		totalReceived, totalSent *big.Int
   687  		nonce                    string
   688  		unconfirmedTxs           int
   689  		nonTokenTxs              int
   690  		totalResults             int
   691  	)
   692  	addrDesc, address, err := w.getAddrDescAndNormalizeAddress(address)
   693  	if err != nil {
   694  		return nil, err
   695  	}
   696  	if w.chainType == bchain.ChainEthereumType {
   697  		var n uint64
   698  		ba, tokens, erc20c, n, nonTokenTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
   699  		if err != nil {
   700  			return nil, err
   701  		}
   702  		nonce = strconv.Itoa(int(n))
   703  	} else {
   704  		// ba can be nil if the address is only in mempool!
   705  		ba, err = w.db.GetAddrDescBalance(addrDesc, db.AddressBalanceDetailNoUTXO)
   706  		if err != nil {
   707  			return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
   708  		}
   709  		if ba != nil {
   710  			// totalResults is known only if there is no filter
   711  			if filter.Vout == AddressFilterVoutOff && filter.FromHeight == 0 && filter.ToHeight == 0 {
   712  				totalResults = int(ba.Txs)
   713  			} else {
   714  				totalResults = -1
   715  			}
   716  		}
   717  	}
   718  	// if there are only unconfirmed transactions, there is no paging
   719  	if ba == nil {
   720  		ba = &db.AddrBalance{}
   721  		page = 0
   722  	}
   723  	// process mempool, only if toHeight is not specified
   724  	if filter.ToHeight == 0 && !filter.OnlyConfirmed {
   725  		txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt)
   726  		if err != nil {
   727  			return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
   728  		}
   729  		for _, txid := range txm {
   730  			tx, err := w.GetTransaction(txid, false, false)
   731  			// mempool transaction may fail
   732  			if err != nil || tx == nil {
   733  				glog.Warning("GetTransaction in mempool: ", err)
   734  			} else {
   735  				// skip already confirmed txs, mempool may be out of sync
   736  				if tx.Confirmations == 0 {
   737  					unconfirmedTxs++
   738  					uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
   739  					uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
   740  					if page == 0 {
   741  						if option == AccountDetailsTxidHistory {
   742  							txids = append(txids, tx.Txid)
   743  						} else if option >= AccountDetailsTxHistoryLight {
   744  							txs = append(txs, tx)
   745  						}
   746  					}
   747  				}
   748  			}
   749  		}
   750  	}
   751  	// get tx history if requested by option or check mempool if there are some transactions for a new address
   752  	if option >= AccountDetailsTxidHistory {
   753  		txc, err := w.getAddressTxids(addrDesc, false, filter, (page+1)*txsOnPage)
   754  		if err != nil {
   755  			return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
   756  		}
   757  		bestheight, _, err := w.db.GetBestBlock()
   758  		if err != nil {
   759  			return nil, errors.Annotatef(err, "GetBestBlock")
   760  		}
   761  		var from, to int
   762  		pg, from, to, page = computePaging(len(txc), page, txsOnPage)
   763  		if len(txc) >= txsOnPage {
   764  			if totalResults < 0 {
   765  				pg.TotalPages = -1
   766  			} else {
   767  				pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
   768  			}
   769  		}
   770  		for i := from; i < to; i++ {
   771  			txid := txc[i]
   772  			if option == AccountDetailsTxidHistory {
   773  				txids = append(txids, txid)
   774  			} else {
   775  				tx, err := w.txFromTxid(txid, bestheight, option, nil)
   776  				if err != nil {
   777  					return nil, err
   778  				}
   779  				txs = append(txs, tx)
   780  			}
   781  		}
   782  	}
   783  	if w.chainType == bchain.ChainBitcoinType {
   784  		totalReceived = ba.ReceivedSat()
   785  		totalSent = &ba.SentSat
   786  	}
   787  	r := &Address{
   788  		Paging:                pg,
   789  		AddrStr:               address,
   790  		BalanceSat:            (*Amount)(&ba.BalanceSat),
   791  		TotalReceivedSat:      (*Amount)(totalReceived),
   792  		TotalSentSat:          (*Amount)(totalSent),
   793  		Txs:                   int(ba.Txs),
   794  		NonTokenTxs:           nonTokenTxs,
   795  		UnconfirmedBalanceSat: (*Amount)(&uBalSat),
   796  		UnconfirmedTxs:        unconfirmedTxs,
   797  		Transactions:          txs,
   798  		Txids:                 txids,
   799  		Tokens:                tokens,
   800  		Erc20Contract:         erc20c,
   801  		Nonce:                 nonce,
   802  	}
   803  	glog.Info("GetAddress ", address, " finished in ", time.Since(start))
   804  	return r, nil
   805  }
   806  
   807  func (w *Worker) balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp int64) (uint32, uint32, uint32, uint32) {
   808  	fromUnix := uint32(0)
   809  	toUnix := maxUint32
   810  	fromHeight := uint32(0)
   811  	toHeight := maxUint32
   812  	if fromTimestamp != 0 {
   813  		fromUnix = uint32(fromTimestamp)
   814  		fromHeight = w.is.GetBlockHeightOfTime(fromUnix)
   815  	}
   816  	if toTimestamp != 0 {
   817  		toUnix = uint32(toTimestamp)
   818  		toHeight = w.is.GetBlockHeightOfTime(toUnix)
   819  	}
   820  	return fromUnix, fromHeight, toUnix, toHeight
   821  }
   822  
   823  func (w *Worker) balanceHistoryForTxid(addrDesc bchain.AddressDescriptor, txid string, fromUnix, toUnix uint32) (*BalanceHistory, error) {
   824  	var time uint32
   825  	var err error
   826  	var ta *db.TxAddresses
   827  	var bchainTx *bchain.Tx
   828  	var height uint32
   829  	if w.chainType == bchain.ChainBitcoinType {
   830  		ta, err = w.db.GetTxAddresses(txid)
   831  		if err != nil {
   832  			return nil, err
   833  		}
   834  		if ta == nil {
   835  			glog.Warning("DB inconsistency:  tx ", txid, ": not found in txAddresses")
   836  			return nil, nil
   837  		}
   838  		height = ta.Height
   839  	} else if w.chainType == bchain.ChainEthereumType {
   840  		var h int
   841  		bchainTx, h, err = w.txCache.GetTransaction(txid)
   842  		if err != nil {
   843  			return nil, err
   844  		}
   845  		if bchainTx == nil {
   846  			glog.Warning("Inconsistency:  tx ", txid, ": not found in the blockchain")
   847  			return nil, nil
   848  		}
   849  		height = uint32(h)
   850  	}
   851  	time = w.is.GetBlockTime(height)
   852  	if time < fromUnix || time >= toUnix {
   853  		return nil, nil
   854  	}
   855  	bh := BalanceHistory{
   856  		Time:        time,
   857  		Txs:         1,
   858  		SentSat:     &Amount{},
   859  		ReceivedSat: &Amount{},
   860  		Txid:        txid,
   861  	}
   862  	if w.chainType == bchain.ChainBitcoinType {
   863  		for i := range ta.Inputs {
   864  			tai := &ta.Inputs[i]
   865  			if bytes.Equal(addrDesc, tai.AddrDesc) {
   866  				(*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &tai.ValueSat)
   867  			}
   868  		}
   869  		for i := range ta.Outputs {
   870  			tao := &ta.Outputs[i]
   871  			if bytes.Equal(addrDesc, tao.AddrDesc) {
   872  				(*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &tao.ValueSat)
   873  			}
   874  		}
   875  	} else if w.chainType == bchain.ChainEthereumType {
   876  		var value big.Int
   877  		ethTxData := eth.GetEthereumTxData(bchainTx)
   878  		// add received amount only for OK transactions
   879  		if ethTxData.Status == 1 {
   880  			if len(bchainTx.Vout) > 0 {
   881  				bchainVout := &bchainTx.Vout[0]
   882  				value = bchainVout.ValueSat
   883  				if len(bchainVout.ScriptPubKey.Addresses) > 0 {
   884  					txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVout.ScriptPubKey.Addresses[0])
   885  					if err != nil {
   886  						return nil, err
   887  					}
   888  					if bytes.Equal(addrDesc, txAddrDesc) {
   889  						(*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &value)
   890  					}
   891  				}
   892  			}
   893  		}
   894  		for i := range bchainTx.Vin {
   895  			bchainVin := &bchainTx.Vin[i]
   896  			if len(bchainVin.Addresses) > 0 {
   897  				txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0])
   898  				if err != nil {
   899  					return nil, err
   900  				}
   901  				if bytes.Equal(addrDesc, txAddrDesc) {
   902  					// add sent amount only for OK transactions, however fees always
   903  					if ethTxData.Status == 1 {
   904  						(*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &value)
   905  					}
   906  					var feesSat big.Int
   907  					// mempool txs do not have fees yet
   908  					if ethTxData.GasUsed != nil {
   909  						feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed)
   910  					}
   911  					(*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &feesSat)
   912  				}
   913  			}
   914  		}
   915  	}
   916  	return &bh, nil
   917  }
   918  
   919  func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, currencies []string) error {
   920  	for i := range histories {
   921  		bh := &histories[i]
   922  		t := time.Unix(int64(bh.Time), 0)
   923  		ticker, err := w.db.FiatRatesFindTicker(&t)
   924  		if err != nil {
   925  			glog.Errorf("Error finding ticker by date %v. Error: %v", t, err)
   926  			continue
   927  		} else if ticker == nil {
   928  			continue
   929  		}
   930  		if len(currencies) == 0 {
   931  			bh.FiatRates = ticker.Rates
   932  		} else {
   933  			rates := make(map[string]float64)
   934  			for _, currency := range currencies {
   935  				currency = strings.ToLower(currency)
   936  				if rate, found := ticker.Rates[currency]; found {
   937  					rates[currency] = rate
   938  				} else {
   939  					rates[currency] = -1
   940  				}
   941  			}
   942  			bh.FiatRates = rates
   943  		}
   944  	}
   945  	return nil
   946  }
   947  
   948  // GetBalanceHistory returns history of balance for given address
   949  func (w *Worker) GetBalanceHistory(address string, fromTimestamp, toTimestamp int64, currencies []string, groupBy uint32) (BalanceHistories, error) {
   950  	currencies = removeEmpty(currencies)
   951  	bhs := make(BalanceHistories, 0)
   952  	start := time.Now()
   953  	addrDesc, _, err := w.getAddrDescAndNormalizeAddress(address)
   954  	if err != nil {
   955  		return nil, err
   956  	}
   957  	fromUnix, fromHeight, toUnix, toHeight := w.balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp)
   958  	if fromHeight >= toHeight {
   959  		return bhs, nil
   960  	}
   961  	txs, err := w.getAddressTxids(addrDesc, false, &AddressFilter{Vout: AddressFilterVoutOff, FromHeight: fromHeight, ToHeight: toHeight}, maxInt)
   962  	if err != nil {
   963  		return nil, err
   964  	}
   965  	for txi := len(txs) - 1; txi >= 0; txi-- {
   966  		bh, err := w.balanceHistoryForTxid(addrDesc, txs[txi], fromUnix, toUnix)
   967  		if err != nil {
   968  			return nil, err
   969  		}
   970  		if bh != nil {
   971  			bhs = append(bhs, *bh)
   972  		}
   973  	}
   974  	bha := bhs.SortAndAggregate(groupBy)
   975  	err = w.setFiatRateToBalanceHistories(bha, currencies)
   976  	if err != nil {
   977  		return nil, err
   978  	}
   979  	glog.Info("GetBalanceHistory ", address, ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), " finished in ", time.Since(start))
   980  	return bha, nil
   981  }
   982  
   983  func (w *Worker) waitForBackendSync() {
   984  	// wait a short time if blockbook is synchronizing with backend
   985  	inSync, _, _ := w.is.GetSyncState()
   986  	count := 30
   987  	for !inSync && count > 0 {
   988  		time.Sleep(time.Millisecond * 100)
   989  		count--
   990  		inSync, _, _ = w.is.GetSyncState()
   991  	}
   992  }
   993  
   994  func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrBalance, onlyConfirmed bool, onlyMempool bool) (Utxos, error) {
   995  	w.waitForBackendSync()
   996  	var err error
   997  	utxos := make(Utxos, 0, 8)
   998  	// store txids from mempool so that they are not added twice in case of import of new block while processing utxos, issue #275
   999  	inMempool := make(map[string]struct{})
  1000  	// outputs could be spent in mempool, record and check mempool spends
  1001  	spentInMempool := make(map[string]struct{})
  1002  	if !onlyConfirmed {
  1003  		// get utxo from mempool
  1004  		txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt)
  1005  		if err != nil {
  1006  			return nil, err
  1007  		}
  1008  		if len(txm) > 0 {
  1009  			mc := make([]*bchain.Tx, len(txm))
  1010  			for i, txid := range txm {
  1011  				// get mempool txs and process their inputs to detect spends between mempool txs
  1012  				bchainTx, _, err := w.txCache.GetTransaction(txid)
  1013  				// mempool transaction may fail
  1014  				if err != nil {
  1015  					glog.Error("GetTransaction in mempool ", txid, ": ", err)
  1016  				} else {
  1017  					mc[i] = bchainTx
  1018  					// get outputs spent by the mempool tx
  1019  					for i := range bchainTx.Vin {
  1020  						vin := &bchainTx.Vin[i]
  1021  						spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{}
  1022  					}
  1023  				}
  1024  			}
  1025  			for _, bchainTx := range mc {
  1026  				if bchainTx != nil {
  1027  					for i := range bchainTx.Vout {
  1028  						vout := &bchainTx.Vout[i]
  1029  						vad, err := w.chainParser.GetAddrDescFromVout(vout)
  1030  						if err == nil && bytes.Equal(addrDesc, vad) {
  1031  							// report only outpoints that are not spent in mempool
  1032  							_, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)]
  1033  							if !e {
  1034  								coinbase := false
  1035  								if len(bchainTx.Vin) == 1 && len(bchainTx.Vin[0].Coinbase) > 0 {
  1036  									coinbase = true
  1037  								}
  1038  								utxos = append(utxos, Utxo{
  1039  									Txid:      bchainTx.Txid,
  1040  									Vout:      int32(i),
  1041  									AmountSat: (*Amount)(&vout.ValueSat),
  1042  									Locktime:  bchainTx.LockTime,
  1043  									Coinbase:  coinbase,
  1044  								})
  1045  								inMempool[bchainTx.Txid] = struct{}{}
  1046  							}
  1047  						}
  1048  					}
  1049  				}
  1050  			}
  1051  		}
  1052  	}
  1053  	if !onlyMempool {
  1054  		// get utxo from index
  1055  		if ba == nil {
  1056  			ba, err = w.db.GetAddrDescBalance(addrDesc, db.AddressBalanceDetailUTXO)
  1057  			if err != nil {
  1058  				return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
  1059  			}
  1060  		}
  1061  		// ba can be nil if the address is only in mempool!
  1062  		if ba != nil && len(ba.Utxos) > 0 {
  1063  			b, _, err := w.db.GetBestBlock()
  1064  			if err != nil {
  1065  				return nil, err
  1066  			}
  1067  			bestheight := int(b)
  1068  			var checksum big.Int
  1069  			checksum.Set(&ba.BalanceSat)
  1070  			// go backwards to get the newest first
  1071  			for i := len(ba.Utxos) - 1; i >= 0; i-- {
  1072  				utxo := &ba.Utxos[i]
  1073  				txid, err := w.chainParser.UnpackTxid(utxo.BtxID)
  1074  				if err != nil {
  1075  					return nil, err
  1076  				}
  1077  				_, e := spentInMempool[txid+strconv.Itoa(int(utxo.Vout))]
  1078  				if !e {
  1079  					confirmations := bestheight - int(utxo.Height) + 1
  1080  					coinbase := false
  1081  					// for performance reasons, check coinbase transactions only in minimum confirmantion range
  1082  					if confirmations < w.chainParser.MinimumCoinbaseConfirmations() {
  1083  						ta, err := w.db.GetTxAddresses(txid)
  1084  						if err != nil {
  1085  							return nil, err
  1086  						}
  1087  						if len(ta.Inputs) == 1 && len(ta.Inputs[0].AddrDesc) == 0 && IsZeroBigInt(&ta.Inputs[0].ValueSat) {
  1088  							coinbase = true
  1089  						}
  1090  					}
  1091  					_, e = inMempool[txid]
  1092  					if !e {
  1093  						utxos = append(utxos, Utxo{
  1094  							Txid:          txid,
  1095  							Vout:          utxo.Vout,
  1096  							AmountSat:     (*Amount)(&utxo.ValueSat),
  1097  							Height:        int(utxo.Height),
  1098  							Confirmations: confirmations,
  1099  							Coinbase:      coinbase,
  1100  						})
  1101  					}
  1102  				}
  1103  				checksum.Sub(&checksum, &utxo.ValueSat)
  1104  			}
  1105  			if checksum.Uint64() != 0 {
  1106  				glog.Warning("DB inconsistency:  ", addrDesc, ": checksum is not zero, checksum=", checksum.Int64())
  1107  			}
  1108  		}
  1109  	}
  1110  	return utxos, nil
  1111  }
  1112  
  1113  // GetAddressUtxo returns unspent outputs for given address
  1114  func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) (Utxos, error) {
  1115  	if w.chainType != bchain.ChainBitcoinType {
  1116  		return nil, NewAPIError("Not supported", true)
  1117  	}
  1118  	start := time.Now()
  1119  	addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
  1120  	if err != nil {
  1121  		return nil, NewAPIError(fmt.Sprintf("Invalid address '%v', %v", address, err), true)
  1122  	}
  1123  	r, err := w.getAddrDescUtxo(addrDesc, nil, onlyConfirmed, false)
  1124  	if err != nil {
  1125  		return nil, err
  1126  	}
  1127  	glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start))
  1128  	return r, nil
  1129  }
  1130  
  1131  // GetBlocks returns BlockInfo for blocks on given page
  1132  func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) {
  1133  	start := time.Now()
  1134  	page--
  1135  	if page < 0 {
  1136  		page = 0
  1137  	}
  1138  	b, _, err := w.db.GetBestBlock()
  1139  	bestheight := int(b)
  1140  	if err != nil {
  1141  		return nil, errors.Annotatef(err, "GetBestBlock")
  1142  	}
  1143  	pg, from, to, page := computePaging(bestheight+1, page, blocksOnPage)
  1144  	r := &Blocks{Paging: pg}
  1145  	r.Blocks = make([]db.BlockInfo, to-from)
  1146  	for i := from; i < to; i++ {
  1147  		bi, err := w.db.GetBlockInfo(uint32(bestheight - i))
  1148  		if err != nil {
  1149  			return nil, err
  1150  		}
  1151  		if bi == nil {
  1152  			r.Blocks = r.Blocks[:i]
  1153  			break
  1154  		}
  1155  		r.Blocks[i-from] = *bi
  1156  	}
  1157  	glog.Info("GetBlocks page ", page, " finished in ", time.Since(start))
  1158  	return r, nil
  1159  }
  1160  
  1161  // removeEmpty removes empty strings from a slice
  1162  func removeEmpty(stringSlice []string) []string {
  1163  	var ret []string
  1164  	for _, str := range stringSlice {
  1165  		if str != "" {
  1166  			ret = append(ret, str)
  1167  		}
  1168  	}
  1169  	return ret
  1170  }
  1171  
  1172  // getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result
  1173  func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRatesTicker) (*db.ResultTickerAsString, error) {
  1174  	currencies = removeEmpty(currencies)
  1175  	if len(currencies) == 0 {
  1176  		// Return all available ticker rates
  1177  		return &db.ResultTickerAsString{
  1178  			Timestamp: ticker.Timestamp.UTC().Unix(),
  1179  			Rates:     ticker.Rates,
  1180  		}, nil
  1181  	}
  1182  	// Check if currencies from the list are available in the ticker rates
  1183  	rates := make(map[string]float64)
  1184  	for _, currency := range currencies {
  1185  		currency = strings.ToLower(currency)
  1186  		if rate, found := ticker.Rates[currency]; found {
  1187  			rates[currency] = rate
  1188  		} else {
  1189  			rates[currency] = -1
  1190  		}
  1191  	}
  1192  	return &db.ResultTickerAsString{
  1193  		Timestamp: ticker.Timestamp.UTC().Unix(),
  1194  		Rates:     rates,
  1195  	}, nil
  1196  }
  1197  
  1198  // GetFiatRatesForBlockID returns fiat rates for block height or block hash
  1199  func (w *Worker) GetFiatRatesForBlockID(bid string, currencies []string) (*db.ResultTickerAsString, error) {
  1200  	var ticker *db.CurrencyRatesTicker
  1201  	bi, err := w.getBlockInfoFromBlockID(bid)
  1202  	if err != nil {
  1203  		if err == bchain.ErrBlockNotFound {
  1204  			return nil, NewAPIError(fmt.Sprintf("Block %v not found", bid), true)
  1205  		}
  1206  		return nil, NewAPIError(fmt.Sprintf("Block %v not found, error: %v", bid, err), false)
  1207  	}
  1208  	dbi := &db.BlockInfo{Time: bi.Time} // get Unix timestamp from block
  1209  	tm := time.Unix(dbi.Time, 0)        // convert it to Time object
  1210  	ticker, err = w.db.FiatRatesFindTicker(&tm)
  1211  	if err != nil {
  1212  		return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
  1213  	} else if ticker == nil {
  1214  		return nil, NewAPIError(fmt.Sprintf("No tickers available for %s", tm), true)
  1215  	}
  1216  	result, err := w.getFiatRatesResult(currencies, ticker)
  1217  	if err != nil {
  1218  		return nil, err
  1219  	}
  1220  	return result, nil
  1221  }
  1222  
  1223  // GetCurrentFiatRates returns last available fiat rates
  1224  func (w *Worker) GetCurrentFiatRates(currencies []string) (*db.ResultTickerAsString, error) {
  1225  	ticker, err := w.db.FiatRatesFindLastTicker()
  1226  	if err != nil {
  1227  		return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
  1228  	} else if ticker == nil {
  1229  		return nil, NewAPIError(fmt.Sprintf("No tickers found!"), true)
  1230  	}
  1231  	result, err := w.getFiatRatesResult(currencies, ticker)
  1232  	if err != nil {
  1233  		return nil, err
  1234  	}
  1235  	return result, nil
  1236  }
  1237  
  1238  // makeErrorRates returns a map of currrencies, with each value equal to -1
  1239  // used when there was an error finding ticker
  1240  func makeErrorRates(currencies []string) map[string]float64 {
  1241  	rates := make(map[string]float64)
  1242  	for _, currency := range currencies {
  1243  		rates[strings.ToLower(currency)] = -1
  1244  	}
  1245  	return rates
  1246  }
  1247  
  1248  // GetFiatRatesForTimestamps returns fiat rates for each of the provided dates
  1249  func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []string) (*db.ResultTickersAsString, error) {
  1250  	if len(timestamps) == 0 {
  1251  		return nil, NewAPIError("No timestamps provided", true)
  1252  	}
  1253  	currencies = removeEmpty(currencies)
  1254  
  1255  	ret := &db.ResultTickersAsString{}
  1256  	for _, timestamp := range timestamps {
  1257  		date := time.Unix(timestamp, 0)
  1258  		date = date.UTC()
  1259  		ticker, err := w.db.FiatRatesFindTicker(&date)
  1260  		if err != nil {
  1261  			glog.Errorf("Error finding ticker for date %v. Error: %v", date, err)
  1262  			ret.Tickers = append(ret.Tickers, db.ResultTickerAsString{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
  1263  			continue
  1264  		} else if ticker == nil {
  1265  			ret.Tickers = append(ret.Tickers, db.ResultTickerAsString{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
  1266  			continue
  1267  		}
  1268  		result, err := w.getFiatRatesResult(currencies, ticker)
  1269  		if err != nil {
  1270  			ret.Tickers = append(ret.Tickers, db.ResultTickerAsString{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
  1271  			continue
  1272  		}
  1273  		ret.Tickers = append(ret.Tickers, *result)
  1274  	}
  1275  	return ret, nil
  1276  }
  1277  
  1278  // GetFiatRatesTickersList returns the list of available fiatRates tickers
  1279  func (w *Worker) GetFiatRatesTickersList(timestamp int64) (*db.ResultTickerListAsString, error) {
  1280  	date := time.Unix(timestamp, 0)
  1281  	date = date.UTC()
  1282  
  1283  	ticker, err := w.db.FiatRatesFindTicker(&date)
  1284  	if err != nil {
  1285  		return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
  1286  	} else if ticker == nil {
  1287  		return nil, NewAPIError(fmt.Sprintf("No tickers found for date %v.", date), true)
  1288  	}
  1289  
  1290  	keys := make([]string, 0, len(ticker.Rates))
  1291  	for k := range ticker.Rates {
  1292  		keys = append(keys, k)
  1293  	}
  1294  	sort.Strings(keys) // sort to get deterministic results
  1295  
  1296  	return &db.ResultTickerListAsString{
  1297  		Timestamp: ticker.Timestamp.Unix(),
  1298  		Tickers:   keys,
  1299  	}, nil
  1300  }
  1301  
  1302  // getBlockInfoFromBlockID returns block info from block height or block hash
  1303  func (w *Worker) getBlockInfoFromBlockID(bid string) (*bchain.BlockInfo, error) {
  1304  	// try to decide if passed string (bid) is block height or block hash
  1305  	// if it's a number, must be less than int32
  1306  	var hash string
  1307  	height, err := strconv.Atoi(bid)
  1308  	if err == nil && height < int(maxUint32) {
  1309  		hash, err = w.db.GetBlockHash(uint32(height))
  1310  		if err != nil {
  1311  			hash = bid
  1312  		}
  1313  	} else {
  1314  		hash = bid
  1315  	}
  1316  	if hash == "" {
  1317  		return nil, NewAPIError("Block not found", true)
  1318  	}
  1319  	bi, err := w.chain.GetBlockInfo(hash)
  1320  	return bi, err
  1321  }
  1322  
  1323  // GetFeeStats returns statistics about block fees
  1324  func (w *Worker) GetFeeStats(bid string) (*FeeStats, error) {
  1325  	// txSpecific extends Tx with an additional Size and Vsize info
  1326  	type txSpecific struct {
  1327  		*bchain.Tx
  1328  		Vsize int `json:"vsize,omitempty"`
  1329  		Size  int `json:"size,omitempty"`
  1330  	}
  1331  
  1332  	start := time.Now()
  1333  	bi, err := w.getBlockInfoFromBlockID(bid)
  1334  	if err != nil {
  1335  		if err == bchain.ErrBlockNotFound {
  1336  			return nil, NewAPIError("Block not found", true)
  1337  		}
  1338  		return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true)
  1339  	}
  1340  
  1341  	feesPerKb := make([]int64, 0, len(bi.Txids))
  1342  	totalFeesSat := big.NewInt(0)
  1343  	averageFeePerKb := int64(0)
  1344  
  1345  	for _, txid := range bi.Txids {
  1346  		// Get a raw JSON with transaction details, including size, vsize, hex
  1347  		txSpecificJSON, err := w.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid})
  1348  		if err != nil {
  1349  			return nil, errors.Annotatef(err, "GetTransactionSpecific")
  1350  		}
  1351  
  1352  		// Serialize the raw JSON into TxSpecific struct
  1353  		var txSpec txSpecific
  1354  		err = json.Unmarshal(txSpecificJSON, &txSpec)
  1355  		if err != nil {
  1356  			return nil, errors.Annotatef(err, "Unmarshal")
  1357  		}
  1358  
  1359  		// Calculate the TX size in bytes
  1360  		txSize := 0
  1361  		if txSpec.Vsize > 0 {
  1362  			txSize = txSpec.Vsize
  1363  		} else if txSpec.Size > 0 {
  1364  			txSize = txSpec.Size
  1365  		} else if txSpec.Hex != "" {
  1366  			txSize = len(txSpec.Hex) / 2
  1367  		} else {
  1368  			errMsg := "Cannot determine the transaction size from neither Vsize, Size nor Hex! Txid: " + txid
  1369  			return nil, NewAPIError(errMsg, true)
  1370  		}
  1371  
  1372  		// Get values of TX inputs and outputs
  1373  		txAddresses, err := w.db.GetTxAddresses(txid)
  1374  		if err != nil {
  1375  			return nil, errors.Annotatef(err, "GetTxAddresses")
  1376  		}
  1377  
  1378  		// Calculate total fees in Satoshis
  1379  		feeSat := big.NewInt(0)
  1380  		for _, input := range txAddresses.Inputs {
  1381  			feeSat = feeSat.Add(&input.ValueSat, feeSat)
  1382  		}
  1383  
  1384  		// Zero inputs means it's a Coinbase TX - skip it
  1385  		if feeSat.Cmp(big.NewInt(0)) == 0 {
  1386  			continue
  1387  		}
  1388  
  1389  		for _, output := range txAddresses.Outputs {
  1390  			feeSat = feeSat.Sub(feeSat, &output.ValueSat)
  1391  		}
  1392  		totalFeesSat.Add(totalFeesSat, feeSat)
  1393  
  1394  		// Convert feeSat to fee per kilobyte and add to an array for decile calculation
  1395  		feePerKb := int64(float64(feeSat.Int64()) / float64(txSize) * 1000)
  1396  		averageFeePerKb += feePerKb
  1397  		feesPerKb = append(feesPerKb, feePerKb)
  1398  	}
  1399  
  1400  	var deciles [11]int64
  1401  	n := len(feesPerKb)
  1402  
  1403  	if n > 0 {
  1404  		averageFeePerKb /= int64(n)
  1405  
  1406  		// Sort fees and calculate the deciles
  1407  		sort.Slice(feesPerKb, func(i, j int) bool { return feesPerKb[i] < feesPerKb[j] })
  1408  		for k := 0; k <= 10; k++ {
  1409  			index := int(math.Floor(0.5+float64(k)*float64(n+1)/10)) - 1
  1410  			if index < 0 {
  1411  				index = 0
  1412  			} else if index >= n {
  1413  				index = n - 1
  1414  			}
  1415  			deciles[k] = feesPerKb[index]
  1416  		}
  1417  	}
  1418  
  1419  	glog.Info("GetFeeStats ", bid, " (", len(feesPerKb), " txs) finished in ", time.Since(start))
  1420  
  1421  	return &FeeStats{
  1422  		TxCount:         len(feesPerKb),
  1423  		AverageFeePerKb: averageFeePerKb,
  1424  		TotalFeesSat:    (*Amount)(totalFeesSat),
  1425  		DecilesFeePerKb: deciles,
  1426  	}, nil
  1427  }
  1428  
  1429  // GetBlock returns paged data about block
  1430  func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) {
  1431  	start := time.Now()
  1432  	page--
  1433  	if page < 0 {
  1434  		page = 0
  1435  	}
  1436  	bi, err := w.getBlockInfoFromBlockID(bid)
  1437  	if err != nil {
  1438  		if err == bchain.ErrBlockNotFound {
  1439  			return nil, NewAPIError("Block not found", true)
  1440  		}
  1441  		return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true)
  1442  	}
  1443  	dbi := &db.BlockInfo{
  1444  		Hash:   bi.Hash,
  1445  		Height: bi.Height,
  1446  		Time:   bi.Time,
  1447  	}
  1448  	txCount := len(bi.Txids)
  1449  	bestheight, _, err := w.db.GetBestBlock()
  1450  	if err != nil {
  1451  		return nil, errors.Annotatef(err, "GetBestBlock")
  1452  	}
  1453  	pg, from, to, page := computePaging(txCount, page, txsOnPage)
  1454  	txs := make([]*Tx, to-from)
  1455  	txi := 0
  1456  	for i := from; i < to; i++ {
  1457  		txs[txi], err = w.txFromTxid(bi.Txids[i], bestheight, AccountDetailsTxHistoryLight, dbi)
  1458  		if err != nil {
  1459  			return nil, err
  1460  		}
  1461  		txi++
  1462  	}
  1463  	if bi.Prev == "" && bi.Height != 0 {
  1464  		bi.Prev, _ = w.db.GetBlockHash(bi.Height - 1)
  1465  	}
  1466  	if bi.Next == "" && bi.Height != bestheight {
  1467  		bi.Next, _ = w.db.GetBlockHash(bi.Height + 1)
  1468  	}
  1469  	txs = txs[:txi]
  1470  	bi.Txids = nil
  1471  	glog.Info("GetBlock ", bid, ", page ", page, " finished in ", time.Since(start))
  1472  	return &Block{
  1473  		Paging: pg,
  1474  		BlockInfo: BlockInfo{
  1475  			Hash:          bi.Hash,
  1476  			Prev:          bi.Prev,
  1477  			Next:          bi.Next,
  1478  			Height:        bi.Height,
  1479  			Confirmations: bi.Confirmations,
  1480  			Size:          bi.Size,
  1481  			Time:          bi.Time,
  1482  			Bits:          bi.Bits,
  1483  			Difficulty:    string(bi.Difficulty),
  1484  			MerkleRoot:    bi.MerkleRoot,
  1485  			Nonce:         string(bi.Nonce),
  1486  			Txids:         bi.Txids,
  1487  			Version:       bi.Version,
  1488  		},
  1489  		TxCount:      txCount,
  1490  		Transactions: txs,
  1491  	}, nil
  1492  }
  1493  
  1494  // ComputeFeeStats computes fee distribution in defined blocks and logs them to log
  1495  func (w *Worker) ComputeFeeStats(blockFrom, blockTo int, stopCompute chan os.Signal) error {
  1496  	bestheight, _, err := w.db.GetBestBlock()
  1497  	if err != nil {
  1498  		return errors.Annotatef(err, "GetBestBlock")
  1499  	}
  1500  	for block := blockFrom; block <= blockTo; block++ {
  1501  		hash, err := w.db.GetBlockHash(uint32(block))
  1502  		if err != nil {
  1503  			return err
  1504  		}
  1505  		bi, err := w.chain.GetBlockInfo(hash)
  1506  		if err != nil {
  1507  			return err
  1508  		}
  1509  		// process only blocks with enough transactions
  1510  		if len(bi.Txids) > 20 {
  1511  			dbi := &db.BlockInfo{
  1512  				Hash:   bi.Hash,
  1513  				Height: bi.Height,
  1514  				Time:   bi.Time,
  1515  			}
  1516  			txids := bi.Txids
  1517  			if w.chainType == bchain.ChainBitcoinType {
  1518  				// skip the coinbase transaction
  1519  				txids = txids[1:]
  1520  			}
  1521  			fees := make([]int64, len(txids))
  1522  			sum := int64(0)
  1523  			for i, txid := range txids {
  1524  				select {
  1525  				case <-stopCompute:
  1526  					glog.Info("ComputeFeeStats interrupted at height ", block)
  1527  					return db.ErrOperationInterrupted
  1528  				default:
  1529  					tx, err := w.txFromTxid(txid, bestheight, AccountDetailsTxHistoryLight, dbi)
  1530  					if err != nil {
  1531  						return err
  1532  					}
  1533  					fee := tx.FeesSat.AsInt64()
  1534  					fees[i] = fee
  1535  					sum += fee
  1536  				}
  1537  			}
  1538  			sort.Slice(fees, func(i, j int) bool { return fees[i] < fees[j] })
  1539  			step := float64(len(fees)) / 10
  1540  			percentils := ""
  1541  			for i := float64(0); i < float64(len(fees)+1); i += step {
  1542  				ii := int(math.Round(i))
  1543  				if ii >= len(fees) {
  1544  					ii = len(fees) - 1
  1545  				}
  1546  				percentils += "," + strconv.FormatInt(fees[ii], 10)
  1547  			}
  1548  			glog.Info(block, ",", time.Unix(bi.Time, 0).Format(time.RFC3339), ",", len(bi.Txids), ",", sum, ",", float64(sum)/float64(len(bi.Txids)), percentils)
  1549  		}
  1550  	}
  1551  	return nil
  1552  }
  1553  
  1554  // GetSystemInfo returns information about system
  1555  func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) {
  1556  	start := time.Now()
  1557  	vi := common.GetVersionInfo()
  1558  	inSync, bestHeight, lastBlockTime := w.is.GetSyncState()
  1559  	inSyncMempool, lastMempoolTime, mempoolSize := w.is.GetMempoolSyncState()
  1560  	ci, err := w.chain.GetChainInfo()
  1561  	var backendError string
  1562  	if err != nil {
  1563  		glog.Error("GetChainInfo error ", err)
  1564  		backendError = errors.Annotatef(err, "GetChainInfo").Error()
  1565  		ci = &bchain.ChainInfo{}
  1566  		// set not in sync in case of backend error
  1567  		inSync = false
  1568  		inSyncMempool = false
  1569  	}
  1570  	var columnStats []common.InternalStateColumn
  1571  	var internalDBSize int64
  1572  	if internal {
  1573  		columnStats = w.is.GetAllDBColumnStats()
  1574  		internalDBSize = w.is.DBSizeTotal()
  1575  	}
  1576  	blockbookInfo := &BlockbookInfo{
  1577  		Coin:              w.is.Coin,
  1578  		Host:              w.is.Host,
  1579  		Version:           vi.Version,
  1580  		GitCommit:         vi.GitCommit,
  1581  		BuildTime:         vi.BuildTime,
  1582  		SyncMode:          w.is.SyncMode,
  1583  		InitialSync:       w.is.InitialSync,
  1584  		InSync:            inSync,
  1585  		BestHeight:        bestHeight,
  1586  		LastBlockTime:     lastBlockTime,
  1587  		InSyncMempool:     inSyncMempool,
  1588  		LastMempoolTime:   lastMempoolTime,
  1589  		MempoolSize:       mempoolSize,
  1590  		Decimals:          w.chainParser.AmountDecimals(),
  1591  		DbSize:            w.db.DatabaseSizeOnDisk(),
  1592  		DbSizeFromColumns: internalDBSize,
  1593  		DbColumns:         columnStats,
  1594  		About:             Text.BlockbookAbout,
  1595  	}
  1596  	backendInfo := &BackendInfo{
  1597  		BackendError:    backendError,
  1598  		BestBlockHash:   ci.Bestblockhash,
  1599  		Blocks:          ci.Blocks,
  1600  		Chain:           ci.Chain,
  1601  		Difficulty:      ci.Difficulty,
  1602  		Headers:         ci.Headers,
  1603  		ProtocolVersion: ci.ProtocolVersion,
  1604  		SizeOnDisk:      ci.SizeOnDisk,
  1605  		Subversion:      ci.Subversion,
  1606  		Timeoffset:      ci.Timeoffset,
  1607  		Version:         ci.Version,
  1608  		Warnings:        ci.Warnings,
  1609  	}
  1610  	glog.Info("GetSystemInfo finished in ", time.Since(start))
  1611  	return &SystemInfo{blockbookInfo, backendInfo}, nil
  1612  }
  1613  
  1614  // GetMempool returns a page of mempool txids
  1615  func (w *Worker) GetMempool(page int, itemsOnPage int) (*MempoolTxids, error) {
  1616  	page--
  1617  	if page < 0 {
  1618  		page = 0
  1619  	}
  1620  	entries := w.mempool.GetAllEntries()
  1621  	pg, from, to, _ := computePaging(len(entries), page, itemsOnPage)
  1622  	r := &MempoolTxids{
  1623  		Paging:      pg,
  1624  		MempoolSize: len(entries),
  1625  	}
  1626  	r.Mempool = make([]MempoolTxid, to-from)
  1627  	for i := from; i < to; i++ {
  1628  		entry := &entries[i]
  1629  		r.Mempool[i-from] = MempoolTxid{
  1630  			Txid: entry.Txid,
  1631  			Time: int64(entry.Time),
  1632  		}
  1633  	}
  1634  	return r, nil
  1635  }