github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/api/worker.go (about)

     1  package api
     2  
     3  import (
     4  	"blockbook/bchain"
     5  	"blockbook/common"
     6  	"blockbook/db"
     7  	"bytes"
     8  	"fmt"
     9  	"math/big"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/golang/glog"
    14  	"github.com/juju/errors"
    15  )
    16  
    17  // Worker is handle to api worker
    18  type Worker struct {
    19  	db          *db.RocksDB
    20  	txCache     *db.TxCache
    21  	chain       bchain.BlockChain
    22  	chainParser bchain.BlockChainParser
    23  	is          *common.InternalState
    24  }
    25  
    26  // NewWorker creates new api worker
    27  func NewWorker(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*Worker, error) {
    28  	w := &Worker{
    29  		db:          db,
    30  		txCache:     txCache,
    31  		chain:       chain,
    32  		chainParser: chain.GetChainParser(),
    33  		is:          is,
    34  	}
    35  	return w, nil
    36  }
    37  
    38  func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescriptor, []string, bool, error) {
    39  	addrDesc, err := w.chainParser.GetAddrDescFromVout(vout)
    40  	if err != nil {
    41  		return nil, nil, false, err
    42  	}
    43  	a, s, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
    44  	return addrDesc, a, s, err
    45  }
    46  
    47  // setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
    48  // there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
    49  func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
    50  	err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
    51  		if isOutput == false {
    52  			tsp, err := w.db.GetTxAddresses(t)
    53  			if err != nil {
    54  				return err
    55  			} else if tsp == nil {
    56  				glog.Warning("DB inconsistency:  tx ", t, ": not found in txAddresses")
    57  			} else if len(tsp.Inputs) > int(index) {
    58  				if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 {
    59  					spentTx, spentHeight, err := w.txCache.GetTransaction(t)
    60  					if err != nil {
    61  						glog.Warning("Tx ", t, ": not found")
    62  					} else {
    63  						if len(spentTx.Vin) > int(index) {
    64  							if spentTx.Vin[index].Txid == txid {
    65  								vout.SpentTxID = t
    66  								vout.SpentHeight = int(spentHeight)
    67  								vout.SpentIndex = int(index)
    68  								return &db.StopIteration{}
    69  							}
    70  						}
    71  					}
    72  				}
    73  			}
    74  		}
    75  		return nil
    76  	})
    77  	return err
    78  }
    79  
    80  // GetSpendingTxid returns transaction id of transaction that spent given output
    81  func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
    82  	start := time.Now()
    83  	tx, err := w.GetTransaction(txid, false)
    84  	if err != nil {
    85  		return "", err
    86  	}
    87  	if n >= len(tx.Vout) || n < 0 {
    88  		return "", NewAPIError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false)
    89  	}
    90  	err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight))
    91  	if err != nil {
    92  		return "", err
    93  	}
    94  	glog.Info("GetSpendingTxid ", txid, " ", n, " finished in ", time.Since(start))
    95  	return tx.Vout[n].SpentTxID, nil
    96  }
    97  
    98  // GetTransaction reads transaction data from txid
    99  func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) {
   100  	start := time.Now()
   101  	bchainTx, height, err := w.txCache.GetTransaction(txid)
   102  	if err != nil {
   103  		return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true)
   104  	}
   105  	var ta *db.TxAddresses
   106  	var blockhash string
   107  	if bchainTx.Confirmations > 0 {
   108  		ta, err = w.db.GetTxAddresses(txid)
   109  		if err != nil {
   110  			return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
   111  		}
   112  		blockhash, err = w.db.GetBlockHash(height)
   113  		if err != nil {
   114  			return nil, errors.Annotatef(err, "GetBlockHash %v", height)
   115  		}
   116  	}
   117  	var valInSat, valOutSat, feesSat big.Int
   118  	vins := make([]Vin, len(bchainTx.Vin))
   119  	for i := range bchainTx.Vin {
   120  		bchainVin := &bchainTx.Vin[i]
   121  		vin := &vins[i]
   122  		vin.Txid = bchainVin.Txid
   123  		vin.N = i
   124  		vin.Vout = bchainVin.Vout
   125  		vin.Sequence = int64(bchainVin.Sequence)
   126  		vin.ScriptSig.Hex = bchainVin.ScriptSig.Hex
   127  		//  bchainVin.Txid=="" is coinbase transaction
   128  		if bchainVin.Txid != "" {
   129  			// load spending addresses from TxAddresses
   130  			tas, err := w.db.GetTxAddresses(bchainVin.Txid)
   131  			if err != nil {
   132  				return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid)
   133  			}
   134  			if tas == nil {
   135  				// mempool transactions are not in TxAddresses but confirmed should be there, log a problem
   136  				if bchainTx.Confirmations > 0 {
   137  					glog.Warning("DB inconsistency:  tx ", bchainVin.Txid, ": not found in txAddresses")
   138  				}
   139  				// try to load from backend
   140  				otx, _, err := w.txCache.GetTransaction(bchainVin.Txid)
   141  				if err != nil {
   142  					return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid)
   143  				}
   144  				if len(otx.Vout) > int(vin.Vout) {
   145  					vout := &otx.Vout[vin.Vout]
   146  					vin.ValueSat = vout.ValueSat
   147  					vin.AddrDesc, vin.Addresses, vin.Searchable, err = w.getAddressesFromVout(vout)
   148  					if err != nil {
   149  						glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout)
   150  					}
   151  				}
   152  			} else {
   153  				if len(tas.Outputs) > int(vin.Vout) {
   154  					output := &tas.Outputs[vin.Vout]
   155  					vin.ValueSat = output.ValueSat
   156  					vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat)
   157  					vin.AddrDesc = output.AddrDesc
   158  					vin.Addresses, vin.Searchable, err = output.Addresses(w.chainParser)
   159  					if err != nil {
   160  						glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i)
   161  					}
   162  				}
   163  			}
   164  			vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat)
   165  			valInSat.Add(&valInSat, &vin.ValueSat)
   166  		}
   167  	}
   168  	vouts := make([]Vout, len(bchainTx.Vout))
   169  	for i := range bchainTx.Vout {
   170  		bchainVout := &bchainTx.Vout[i]
   171  		vout := &vouts[i]
   172  		vout.N = i
   173  		vout.ValueSat = bchainVout.ValueSat
   174  		vout.Value = w.chainParser.AmountToDecimalString(&bchainVout.ValueSat)
   175  		valOutSat.Add(&valOutSat, &bchainVout.ValueSat)
   176  		vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex
   177  		vout.ScriptPubKey.AddrDesc, vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = w.getAddressesFromVout(bchainVout)
   178  		if err != nil {
   179  			glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, bchainTx.Txid, bchainVout.N)
   180  		}
   181  		if ta != nil {
   182  			vout.Spent = ta.Outputs[i].Spent
   183  			if spendingTxs && vout.Spent {
   184  				err = w.setSpendingTxToVout(vout, bchainTx.Txid, height)
   185  				if err != nil {
   186  					glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.ScriptPubKey.AddrDesc, vout.N)
   187  				}
   188  			}
   189  		}
   190  	}
   191  	// for coinbase transactions valIn is 0
   192  	feesSat.Sub(&valInSat, &valOutSat)
   193  	if feesSat.Sign() == -1 {
   194  		feesSat.SetUint64(0)
   195  	}
   196  	// for now do not return size, we would have to compute vsize of segwit transactions
   197  	// size:=len(bchainTx.Hex) / 2
   198  	r := &Tx{
   199  		Blockhash:     blockhash,
   200  		Blockheight:   int(height),
   201  		Blocktime:     bchainTx.Blocktime,
   202  		Confirmations: bchainTx.Confirmations,
   203  		Fees:          w.chainParser.AmountToDecimalString(&feesSat),
   204  		FeesSat:       feesSat,
   205  		Locktime:      bchainTx.LockTime,
   206  		Time:          bchainTx.Time,
   207  		Txid:          bchainTx.Txid,
   208  		ValueIn:       w.chainParser.AmountToDecimalString(&valInSat),
   209  		ValueInSat:    valInSat,
   210  		ValueOut:      w.chainParser.AmountToDecimalString(&valOutSat),
   211  		ValueOutSat:   valOutSat,
   212  		Version:       bchainTx.Version,
   213  		Hex:           bchainTx.Hex,
   214  		Vin:           vins,
   215  		Vout:          vouts,
   216  	}
   217  	if spendingTxs {
   218  		glog.Info("GetTransaction ", txid, " finished in ", time.Since(start))
   219  	}
   220  	return r, nil
   221  }
   222  
   223  func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool) ([]string, error) {
   224  	var err error
   225  	txids := make([]string, 0, 4)
   226  	if !mempool {
   227  		err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
   228  			txids = append(txids, txid)
   229  			return nil
   230  		})
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  	} else {
   235  		m, err := w.chain.GetMempoolTransactionsForAddrDesc(addrDesc)
   236  		if err != nil {
   237  			return nil, err
   238  		}
   239  		txids = append(txids, m...)
   240  	}
   241  	return txids, nil
   242  }
   243  
   244  func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int {
   245  	var val big.Int
   246  	for _, vout := range t.Vout {
   247  		if bytes.Equal(vout.ScriptPubKey.AddrDesc, addrDesc) {
   248  			val.Add(&val, &vout.ValueSat)
   249  		}
   250  	}
   251  	return &val
   252  }
   253  
   254  func (t *Tx) getAddrVinValue(addrDesc bchain.AddressDescriptor) *big.Int {
   255  	var val big.Int
   256  	for _, vin := range t.Vin {
   257  		if bytes.Equal(vin.AddrDesc, addrDesc) {
   258  			val.Add(&val, &vin.ValueSat)
   259  		}
   260  	}
   261  	return &val
   262  }
   263  
   264  // UniqueTxidsInReverse reverts the order of transactions (so that newest are first) and removes duplicate transactions
   265  func UniqueTxidsInReverse(txids []string) []string {
   266  	i := len(txids)
   267  	ut := make([]string, i)
   268  	txidsMap := make(map[string]struct{})
   269  	for _, txid := range txids {
   270  		_, e := txidsMap[txid]
   271  		if !e {
   272  			i--
   273  			ut[i] = txid
   274  			txidsMap[txid] = struct{}{}
   275  		}
   276  	}
   277  	return ut[i:]
   278  }
   279  
   280  func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32) *Tx {
   281  	var err error
   282  	var valInSat, valOutSat, feesSat big.Int
   283  	vins := make([]Vin, len(ta.Inputs))
   284  	for i := range ta.Inputs {
   285  		tai := &ta.Inputs[i]
   286  		vin := &vins[i]
   287  		vin.N = i
   288  		vin.ValueSat = tai.ValueSat
   289  		vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat)
   290  		valInSat.Add(&valInSat, &vin.ValueSat)
   291  		vin.Addresses, vin.Searchable, err = tai.Addresses(w.chainParser)
   292  		if err != nil {
   293  			glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai)
   294  		}
   295  	}
   296  	vouts := make([]Vout, len(ta.Outputs))
   297  	for i := range ta.Outputs {
   298  		tao := &ta.Outputs[i]
   299  		vout := &vouts[i]
   300  		vout.N = i
   301  		vout.ValueSat = tao.ValueSat
   302  		vout.Value = w.chainParser.AmountToDecimalString(&vout.ValueSat)
   303  		valOutSat.Add(&valOutSat, &vout.ValueSat)
   304  		vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = tao.Addresses(w.chainParser)
   305  		if err != nil {
   306  			glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao)
   307  		}
   308  		vout.Spent = tao.Spent
   309  	}
   310  	// for coinbase transactions valIn is 0
   311  	feesSat.Sub(&valInSat, &valOutSat)
   312  	if feesSat.Sign() == -1 {
   313  		feesSat.SetUint64(0)
   314  	}
   315  	r := &Tx{
   316  		Blockhash:     bi.Hash,
   317  		Blockheight:   int(ta.Height),
   318  		Blocktime:     bi.Time,
   319  		Confirmations: bestheight - ta.Height + 1,
   320  		Fees:          w.chainParser.AmountToDecimalString(&feesSat),
   321  		Time:          bi.Time,
   322  		Txid:          txid,
   323  		ValueIn:       w.chainParser.AmountToDecimalString(&valInSat),
   324  		ValueOut:      w.chainParser.AmountToDecimalString(&valOutSat),
   325  		Vin:           vins,
   326  		Vout:          vouts,
   327  	}
   328  	return r
   329  }
   330  
   331  func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
   332  	from := page * itemsOnPage
   333  	totalPages := (count - 1) / itemsOnPage
   334  	if totalPages < 0 {
   335  		totalPages = 0
   336  	}
   337  	if from >= count {
   338  		page = totalPages
   339  	}
   340  	from = page * itemsOnPage
   341  	to := (page + 1) * itemsOnPage
   342  	if to > count {
   343  		to = count
   344  	}
   345  	return Paging{
   346  		ItemsOnPage: itemsOnPage,
   347  		Page:        page + 1,
   348  		TotalPages:  totalPages + 1,
   349  	}, from, to, page
   350  }
   351  
   352  // GetAddress computes address value and gets transactions for given address
   353  func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids bool) (*Address, error) {
   354  	start := time.Now()
   355  	page--
   356  	if page < 0 {
   357  		page = 0
   358  	}
   359  	addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
   360  	if err != nil {
   361  		return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
   362  	}
   363  	// ba can be nil if the address is only in mempool!
   364  	ba, err := w.db.GetAddrDescBalance(addrDesc)
   365  	if err != nil {
   366  		return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
   367  	}
   368  	// convert the address to the format defined by the parser
   369  	addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
   370  	if err != nil {
   371  		glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc)
   372  	}
   373  	if len(addresses) == 1 {
   374  		address = addresses[0]
   375  	}
   376  	txc, err := w.getAddressTxids(addrDesc, false)
   377  	if err != nil {
   378  		return nil, errors.Annotatef(err, "getAddressTxids %v false", address)
   379  	}
   380  	txc = UniqueTxidsInReverse(txc)
   381  	var txm []string
   382  	// if there are only unconfirmed transactions, ba is nil
   383  	if ba == nil {
   384  		ba = &db.AddrBalance{}
   385  		page = 0
   386  	}
   387  	txm, err = w.getAddressTxids(addrDesc, true)
   388  	if err != nil {
   389  		return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
   390  	}
   391  	txm = UniqueTxidsInReverse(txm)
   392  	// check if the address exist
   393  	if len(txc)+len(txm) == 0 {
   394  		return &Address{
   395  			AddrStr: address,
   396  		}, nil
   397  	}
   398  	bestheight, _, err := w.db.GetBestBlock()
   399  	if err != nil {
   400  		return nil, errors.Annotatef(err, "GetBestBlock")
   401  	}
   402  	pg, from, to, page := computePaging(len(txc), page, txsOnPage)
   403  	var txs []*Tx
   404  	var txids []string
   405  	if onlyTxids {
   406  		txids = make([]string, len(txm)+to-from)
   407  	} else {
   408  		txs = make([]*Tx, len(txm)+to-from)
   409  	}
   410  	txi := 0
   411  	// load mempool transactions
   412  	var uBalSat big.Int
   413  	for _, tx := range txm {
   414  		tx, err := w.GetTransaction(tx, false)
   415  		// mempool transaction may fail
   416  		if err != nil {
   417  			glog.Error("GetTransaction in mempool ", tx, ": ", err)
   418  		} else {
   419  			uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
   420  			uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
   421  			if page == 0 {
   422  				if onlyTxids {
   423  					txids[txi] = tx.Txid
   424  				} else {
   425  					txs[txi] = tx
   426  				}
   427  				txi++
   428  			}
   429  		}
   430  	}
   431  	if len(txc) != int(ba.Txs) {
   432  		glog.Warning("DB inconsistency for address ", address, ": number of txs from column addresses ", len(txc), ", from addressBalance ", ba.Txs)
   433  	}
   434  	for i := from; i < to; i++ {
   435  		txid := txc[i]
   436  		if onlyTxids {
   437  			txids[txi] = txid
   438  		} else {
   439  			ta, err := w.db.GetTxAddresses(txid)
   440  			if err != nil {
   441  				return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
   442  			}
   443  			if ta == nil {
   444  				glog.Warning("DB inconsistency:  tx ", txid, ": not found in txAddresses")
   445  				continue
   446  			}
   447  			bi, err := w.db.GetBlockInfo(ta.Height)
   448  			if err != nil {
   449  				return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
   450  			}
   451  			if bi == nil {
   452  				glog.Warning("DB inconsistency:  block height ", ta.Height, ": not found in db")
   453  				continue
   454  			}
   455  			txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight)
   456  		}
   457  		txi++
   458  	}
   459  	if onlyTxids {
   460  		txids = txids[:txi]
   461  	} else {
   462  		txs = txs[:txi]
   463  	}
   464  	r := &Address{
   465  		Paging:                  pg,
   466  		AddrStr:                 address,
   467  		Balance:                 w.chainParser.AmountToDecimalString(&ba.BalanceSat),
   468  		TotalReceived:           w.chainParser.AmountToDecimalString(ba.ReceivedSat()),
   469  		TotalSent:               w.chainParser.AmountToDecimalString(&ba.SentSat),
   470  		TxApperances:            len(txc),
   471  		UnconfirmedBalance:      w.chainParser.AmountToDecimalString(&uBalSat),
   472  		UnconfirmedTxApperances: len(txm),
   473  		Transactions:            txs,
   474  		Txids:                   txids,
   475  	}
   476  	glog.Info("GetAddress ", address, " finished in ", time.Since(start))
   477  	return r, nil
   478  }
   479  
   480  // GetAddressUtxo returns unspent outputs for given address
   481  func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUtxo, error) {
   482  	start := time.Now()
   483  	addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
   484  	if err != nil {
   485  		return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
   486  	}
   487  	spentInMempool := make(map[string]struct{})
   488  	r := make([]AddressUtxo, 0, 8)
   489  	if !onlyConfirmed {
   490  		// get utxo from mempool
   491  		txm, err := w.getAddressTxids(addrDesc, true)
   492  		if err != nil {
   493  			return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
   494  		}
   495  		txm = UniqueTxidsInReverse(txm)
   496  		mc := make([]*bchain.Tx, len(txm))
   497  		for i, txid := range txm {
   498  			// get mempool txs and process their inputs to detect spends between mempool txs
   499  			bchainTx, _, err := w.txCache.GetTransaction(txid)
   500  			// mempool transaction may fail
   501  			if err != nil {
   502  				glog.Error("GetTransaction in mempool ", txid, ": ", err)
   503  			} else {
   504  				mc[i] = bchainTx
   505  				// get outputs spent by the mempool tx
   506  				for i := range bchainTx.Vin {
   507  					vin := &bchainTx.Vin[i]
   508  					spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{}
   509  				}
   510  			}
   511  		}
   512  		for _, bchainTx := range mc {
   513  			if bchainTx != nil {
   514  				for i := range bchainTx.Vout {
   515  					vout := &bchainTx.Vout[i]
   516  					vad, err := w.chainParser.GetAddrDescFromVout(vout)
   517  					if err == nil && bytes.Equal(addrDesc, vad) {
   518  						// report only outpoints that are not spent in mempool
   519  						_, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)]
   520  						if !e {
   521  							r = append(r, AddressUtxo{
   522  								Txid:      bchainTx.Txid,
   523  								Vout:      uint32(i),
   524  								AmountSat: vout.ValueSat,
   525  								Amount:    w.chainParser.AmountToDecimalString(&vout.ValueSat),
   526  							})
   527  						}
   528  					}
   529  				}
   530  			}
   531  		}
   532  	}
   533  	// get utxo from index
   534  	ba, err := w.db.GetAddrDescBalance(addrDesc)
   535  	if err != nil {
   536  		return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
   537  	}
   538  	var checksum big.Int
   539  	// ba can be nil if the address is only in mempool!
   540  	if ba != nil && ba.BalanceSat.Uint64() > 0 {
   541  		type outpoint struct {
   542  			txid string
   543  			vout uint32
   544  		}
   545  		outpoints := make([]outpoint, 0, 8)
   546  		err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
   547  			if isOutput {
   548  				outpoints = append(outpoints, outpoint{txid, vout})
   549  			}
   550  			return nil
   551  		})
   552  		if err != nil {
   553  			return nil, err
   554  		}
   555  		var lastTxid string
   556  		var ta *db.TxAddresses
   557  		checksum = ba.BalanceSat
   558  		b, _, err := w.db.GetBestBlock()
   559  		if err != nil {
   560  			return nil, err
   561  		}
   562  		bestheight := int(b)
   563  		for i := len(outpoints) - 1; i >= 0 && checksum.Int64() > 0; i-- {
   564  			o := outpoints[i]
   565  			if lastTxid != o.txid {
   566  				ta, err = w.db.GetTxAddresses(o.txid)
   567  				if err != nil {
   568  					return nil, err
   569  				}
   570  				lastTxid = o.txid
   571  			}
   572  			if ta == nil {
   573  				glog.Warning("DB inconsistency:  tx ", o.txid, ": not found in txAddresses")
   574  			} else {
   575  				if len(ta.Outputs) <= int(o.vout) {
   576  					glog.Warning("DB inconsistency:  txAddresses ", o.txid, " does not have enough outputs")
   577  				} else {
   578  					if !ta.Outputs[o.vout].Spent {
   579  						v := ta.Outputs[o.vout].ValueSat
   580  						// report only outpoints that are not spent in mempool
   581  						_, e := spentInMempool[o.txid+strconv.Itoa(int(o.vout))]
   582  						if !e {
   583  							r = append(r, AddressUtxo{
   584  								Txid:          o.txid,
   585  								Vout:          o.vout,
   586  								AmountSat:     v,
   587  								Amount:        w.chainParser.AmountToDecimalString(&v),
   588  								Height:        int(ta.Height),
   589  								Confirmations: bestheight - int(ta.Height) + 1,
   590  							})
   591  						}
   592  						checksum.Sub(&checksum, &v)
   593  					}
   594  				}
   595  			}
   596  		}
   597  	}
   598  	if checksum.Uint64() != 0 {
   599  		glog.Warning("DB inconsistency:  ", address, ": checksum is not zero")
   600  	}
   601  	glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start))
   602  	return r, nil
   603  }
   604  
   605  // GetBlocks returns BlockInfo for blocks on given page
   606  func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) {
   607  	start := time.Now()
   608  	page--
   609  	if page < 0 {
   610  		page = 0
   611  	}
   612  	b, _, err := w.db.GetBestBlock()
   613  	bestheight := int(b)
   614  	if err != nil {
   615  		return nil, errors.Annotatef(err, "GetBestBlock")
   616  	}
   617  	pg, from, to, page := computePaging(bestheight+1, page, blocksOnPage)
   618  	r := &Blocks{Paging: pg}
   619  	r.Blocks = make([]db.BlockInfo, to-from)
   620  	for i := from; i < to; i++ {
   621  		bi, err := w.db.GetBlockInfo(uint32(bestheight - i))
   622  		if err != nil {
   623  			return nil, err
   624  		}
   625  		if bi == nil {
   626  			r.Blocks = r.Blocks[:i]
   627  			break
   628  		}
   629  		r.Blocks[i-from] = *bi
   630  	}
   631  	glog.Info("GetBlocks page ", page, " finished in ", time.Since(start))
   632  	return r, nil
   633  }
   634  
   635  // GetBlock returns paged data about block
   636  func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) {
   637  	start := time.Now()
   638  	page--
   639  	if page < 0 {
   640  		page = 0
   641  	}
   642  	// try to decide if passed string (bid) is block height or block hash
   643  	// if it's a number, must be less than int32
   644  	var hash string
   645  	height, err := strconv.Atoi(bid)
   646  	if err == nil && height < int(^uint32(0)) {
   647  		hash, err = w.db.GetBlockHash(uint32(height))
   648  		if err != nil {
   649  			hash = bid
   650  		}
   651  	} else {
   652  		hash = bid
   653  	}
   654  	bi, err := w.chain.GetBlockInfo(hash)
   655  	if err != nil {
   656  		if err == bchain.ErrBlockNotFound {
   657  			return nil, NewAPIError("Block not found", true)
   658  		}
   659  		return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true)
   660  	}
   661  	dbi := &db.BlockInfo{
   662  		Hash:   bi.Hash,
   663  		Height: bi.Height,
   664  		Time:   bi.Time,
   665  	}
   666  	txCount := len(bi.Txids)
   667  	bestheight, _, err := w.db.GetBestBlock()
   668  	if err != nil {
   669  		return nil, errors.Annotatef(err, "GetBestBlock")
   670  	}
   671  	pg, from, to, page := computePaging(txCount, page, txsOnPage)
   672  	glog.Info("GetBlock ", bid, ", page ", page, " finished in ", time.Since(start))
   673  	txs := make([]*Tx, to-from)
   674  	txi := 0
   675  	for i := from; i < to; i++ {
   676  		txid := bi.Txids[i]
   677  		ta, err := w.db.GetTxAddresses(txid)
   678  		if err != nil {
   679  			return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
   680  		}
   681  		if ta == nil {
   682  			glog.Warning("DB inconsistency:  tx ", txid, ": not found in txAddresses")
   683  			continue
   684  		}
   685  		txs[txi] = w.txFromTxAddress(txid, ta, dbi, bestheight)
   686  		txi++
   687  	}
   688  	txs = txs[:txi]
   689  	bi.Txids = nil
   690  	return &Block{
   691  		Paging:       pg,
   692  		BlockInfo:    *bi,
   693  		TxCount:      txCount,
   694  		Transactions: txs,
   695  	}, nil
   696  }
   697  
   698  // GetSystemInfo returns information about system
   699  func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) {
   700  	start := time.Now()
   701  	ci, err := w.chain.GetChainInfo()
   702  	if err != nil {
   703  		return nil, errors.Annotatef(err, "GetChainInfo")
   704  	}
   705  	vi := common.GetVersionInfo()
   706  	ss, bh, st := w.is.GetSyncState()
   707  	ms, mt, msz := w.is.GetMempoolSyncState()
   708  	var dbc []common.InternalStateColumn
   709  	var dbs int64
   710  	if internal {
   711  		dbc = w.is.GetAllDBColumnStats()
   712  		dbs = w.is.DBSizeTotal()
   713  	}
   714  	bi := &BlockbookInfo{
   715  		Coin:              w.is.Coin,
   716  		Host:              w.is.Host,
   717  		Version:           vi.Version,
   718  		GitCommit:         vi.GitCommit,
   719  		BuildTime:         vi.BuildTime,
   720  		SyncMode:          w.is.SyncMode,
   721  		InitialSync:       w.is.InitialSync,
   722  		InSync:            ss,
   723  		BestHeight:        bh,
   724  		LastBlockTime:     st,
   725  		InSyncMempool:     ms,
   726  		LastMempoolTime:   mt,
   727  		MempoolSize:       msz,
   728  		DbSize:            w.db.DatabaseSizeOnDisk(),
   729  		DbSizeFromColumns: dbs,
   730  		DbColumns:         dbc,
   731  		About:             Text.BlockbookAbout,
   732  	}
   733  	glog.Info("GetSystemInfo finished in ", time.Since(start))
   734  	return &SystemInfo{bi, ci}, nil
   735  }