blockbook/api/worker.go

417 lines
12 KiB
Go
Raw Normal View History

2018-06-26 05:02:53 -06:00
package api
import (
"blockbook/bchain"
2018-06-27 16:36:56 -06:00
"blockbook/common"
2018-06-26 05:02:53 -06:00
"blockbook/db"
"fmt"
2018-07-24 07:58:37 -06:00
"math/big"
"time"
"github.com/golang/glog"
"github.com/juju/errors"
2018-06-26 05:02:53 -06:00
)
// Worker is handle to api worker
type Worker struct {
db *db.RocksDB
txCache *db.TxCache
chain bchain.BlockChain
chainParser bchain.BlockChainParser
2018-06-27 16:36:56 -06:00
is *common.InternalState
2018-06-26 05:02:53 -06:00
}
// NewWorker creates new api worker
2018-06-27 16:36:56 -06:00
func NewWorker(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*Worker, error) {
2018-06-26 05:02:53 -06:00
w := &Worker{
db: db,
txCache: txCache,
chain: chain,
chainParser: chain.GetChainParser(),
2018-06-27 16:36:56 -06:00
is: is,
2018-06-26 05:02:53 -06:00
}
return w, nil
}
2018-06-27 16:36:56 -06:00
// GetTransaction reads transaction data from txid
func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool) (*Tx, error) {
2018-06-26 05:02:53 -06:00
bchainTx, height, err := w.txCache.GetTransaction(txid, bestheight)
if err != nil {
return nil, NewApiError(fmt.Sprintf("Tx not found, %v", err), true)
2018-06-26 05:02:53 -06:00
}
var blockhash string
if bchainTx.Confirmations > 0 {
blockhash, err = w.db.GetBlockHash(height)
if err != nil {
return nil, errors.Annotatef(err, "GetBlockHash %v", height)
2018-06-26 05:02:53 -06:00
}
}
2018-07-24 07:58:37 -06:00
var valInSat, valOutSat, feesSat big.Int
2018-06-26 05:02:53 -06:00
vins := make([]Vin, len(bchainTx.Vin))
for i := range bchainTx.Vin {
bchainVin := &bchainTx.Vin[i]
vin := &vins[i]
vin.Txid = bchainVin.Txid
vin.N = i
vin.Vout = bchainVin.Vout
vin.ScriptSig.Hex = bchainVin.ScriptSig.Hex
// bchainVin.Txid=="" is coinbase transaction
if bchainVin.Txid != "" {
// load spending addresses from TxAddresses
ta, err := w.db.GetTxAddresses(bchainVin.Txid)
if err != nil {
return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid)
}
if ta == nil {
// mempool transactions are not in TxAddresses, all confirmed should be there, log a problem
if bchainTx.Confirmations > 0 {
glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses")
}
// try to load from backend
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
if err != nil {
return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid)
}
if len(otx.Vout) > int(vin.Vout) {
vout := &otx.Vout[vin.Vout]
vin.ValueSat = vout.ValueSat
if vout.Address != nil {
a := vout.Address.String()
vin.Addr = a
}
}
} else {
if len(ta.Outputs) > int(vin.Vout) {
output := &ta.Outputs[vin.Vout]
vin.ValueSat = output.ValueSat
vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat)
a, _ := output.Addresses(w.chainParser)
if len(a) > 0 {
vin.Addr = a[0]
}
}
2018-06-26 05:02:53 -06:00
}
vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat)
valInSat.Add(&valInSat, &vin.ValueSat)
2018-06-26 05:02:53 -06:00
}
}
vouts := make([]Vout, len(bchainTx.Vout))
for i := range bchainTx.Vout {
bchainVout := &bchainTx.Vout[i]
vout := &vouts[i]
vout.N = i
2018-07-24 07:58:37 -06:00
vout.ValueSat = bchainVout.ValueSat
vout.Value = w.chainParser.AmountToDecimalString(&bchainVout.ValueSat)
valOutSat.Add(&valOutSat, &bchainVout.ValueSat)
2018-06-26 05:02:53 -06:00
vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex
vout.ScriptPubKey.Addresses = bchainVout.ScriptPubKey.Addresses
if spendingTxs {
2018-06-26 05:02:53 -06:00
// TODO
}
}
// for coinbase transactions valIn is 0
2018-07-24 07:58:37 -06:00
feesSat.Sub(&valInSat, &valOutSat)
if feesSat.Sign() == -1 {
feesSat.SetUint64(0)
}
2018-06-26 05:02:53 -06:00
// for now do not return size, we would have to compute vsize of segwit transactions
// size:=len(bchainTx.Hex) / 2
r := &Tx{
Blockhash: blockhash,
Blockheight: int(height),
Blocktime: bchainTx.Blocktime,
Confirmations: bchainTx.Confirmations,
2018-07-24 07:58:37 -06:00
Fees: w.chainParser.AmountToDecimalString(&feesSat),
2018-06-26 05:02:53 -06:00
Locktime: bchainTx.LockTime,
WithSpends: spendingTxs,
2018-06-26 05:02:53 -06:00
Time: bchainTx.Time,
2018-06-28 14:41:46 -06:00
Txid: bchainTx.Txid,
2018-07-24 07:58:37 -06:00
ValueIn: w.chainParser.AmountToDecimalString(&valInSat),
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
2018-06-26 05:02:53 -06:00
Version: bchainTx.Version,
Vin: vins,
Vout: vouts,
}
return r, nil
}
func (w *Worker) getAddressTxids(address string, mempool bool) ([]string, error) {
var err error
txids := make([]string, 0)
if !mempool {
err = w.db.GetTransactions(address, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
txids = append(txids, txid)
return nil
})
if err != nil {
return nil, err
}
} else {
m, err := w.chain.GetMempoolTransactions(address)
if err != nil {
return nil, err
}
txids = append(txids, m...)
}
return txids, nil
}
2018-07-24 07:58:37 -06:00
func (t *Tx) getAddrVoutValue(addrID string) *big.Int {
var val big.Int
for _, vout := range t.Vout {
for _, a := range vout.ScriptPubKey.Addresses {
if a == addrID {
2018-07-24 07:58:37 -06:00
val.Add(&val, &vout.ValueSat)
}
}
}
2018-07-24 07:58:37 -06:00
return &val
}
2018-07-24 07:58:37 -06:00
func (t *Tx) getAddrVinValue(addrID string) *big.Int {
var val big.Int
for _, vin := range t.Vin {
if vin.Addr == addrID {
2018-07-24 07:58:37 -06:00
val.Add(&val, &vin.ValueSat)
}
}
2018-07-24 07:58:37 -06:00
return &val
}
// UniqueTxidsInReverse reverts the order of transactions (so that newest are first) and removes duplicate transactions
func UniqueTxidsInReverse(txids []string) []string {
i := len(txids)
ut := make([]string, i)
txidsMap := make(map[string]struct{})
for _, txid := range txids {
_, e := txidsMap[txid]
if !e {
i--
ut[i] = txid
txidsMap[txid] = struct{}{}
}
}
return ut[i:]
}
func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32) *Tx {
var valInSat, valOutSat, feesSat big.Int
vins := make([]Vin, len(ta.Inputs))
for i := range ta.Inputs {
tai := &ta.Inputs[i]
vin := &vins[i]
vin.N = i
vin.ValueSat = tai.ValueSat
vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat)
valInSat.Add(&valInSat, &vin.ValueSat)
a, err := tai.Addresses(w.chainParser)
2018-08-23 15:20:07 -06:00
if err == nil && len(a) == 1 {
vin.Addr = a[0]
}
}
vouts := make([]Vout, len(ta.Outputs))
for i := range ta.Outputs {
tao := &ta.Outputs[i]
vout := &vouts[i]
vout.N = i
vout.ValueSat = tao.ValueSat
vout.Value = w.chainParser.AmountToDecimalString(&vout.ValueSat)
valOutSat.Add(&valOutSat, &vout.ValueSat)
a, err := tao.Addresses(w.chainParser)
2018-08-23 15:20:07 -06:00
if err == nil {
vout.ScriptPubKey.Addresses = a
}
}
// for coinbase transactions valIn is 0
feesSat.Sub(&valInSat, &valOutSat)
if feesSat.Sign() == -1 {
feesSat.SetUint64(0)
}
r := &Tx{
2018-08-23 15:20:07 -06:00
Blockhash: bi.Hash,
Blockheight: int(ta.Height),
2018-08-23 15:20:07 -06:00
Blocktime: bi.Time,
Confirmations: bestheight - ta.Height + 1,
Fees: w.chainParser.AmountToDecimalString(&feesSat),
2018-08-23 15:20:07 -06:00
Time: bi.Time,
Txid: txid,
ValueIn: w.chainParser.AmountToDecimalString(&valInSat),
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
Vin: vins,
Vout: vouts,
}
return r
}
// GetAddress computes address value and gets transactions for given address
func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids bool) (*Address, error) {
start := time.Now()
if page < 0 {
page = 0
}
ba, err := w.db.GetAddressBalance(address)
if err != nil {
return nil, NewApiError(fmt.Sprintf("Address not found, %v", err), true)
}
if ba == nil {
return nil, NewApiError("Address not found", true)
}
txc, err := w.getAddressTxids(address, false)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v false", address)
}
txc = UniqueTxidsInReverse(txc)
var txm []string
// mempool only on the first page
if page == 0 {
txm, err = w.getAddressTxids(address, true)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
}
txm = UniqueTxidsInReverse(txm)
}
bestheight, _, err := w.db.GetBestBlock()
if err != nil {
return nil, errors.Annotatef(err, "GetBestBlock")
}
// paging
from := page * txsOnPage
totalPages := len(txc) / txsOnPage
if from >= len(txc) {
page = totalPages - 1
if page < 0 {
page = 0
}
}
from = page * txsOnPage
to := (page + 1) * txsOnPage
if to > len(txc) {
to = len(txc)
}
txs := make([]*Tx, len(txm)+to-from)
txi := 0
// load mempool transactions
var uBalSat big.Int
for _, tx := range txm {
tx, err := w.GetTransaction(tx, bestheight, false)
// mempool transaction may fail
if err != nil {
glog.Error("GetTransaction in mempool ", tx, ": ", err)
} else {
uBalSat.Sub(tx.getAddrVoutValue(address), tx.getAddrVinValue(address))
txs[txi] = tx
txi++
}
}
if len(txc) != int(ba.Txs) {
glog.Warning("DB inconsistency for address ", address, ": number of txs from column addresses ", len(txc), ", from addressBalance ", ba.Txs)
}
for i := from; i < to; i++ {
txid := txc[i]
ta, err := w.db.GetTxAddresses(txid)
if err != nil {
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
}
if ta == nil {
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
continue
}
bi, err := w.db.GetBlockInfo(ta.Height)
if err != nil {
return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
}
if bi == nil {
glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db")
continue
}
txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight)
txi++
}
r := &Address{
AddrStr: address,
Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat),
TotalReceived: w.chainParser.AmountToDecimalString(ba.ReceivedSat()),
TotalSent: w.chainParser.AmountToDecimalString(&ba.SentSat),
TxApperances: len(txc),
UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat),
UnconfirmedTxApperances: len(txm),
Transactions: txs[:txi],
Page: page,
TotalPages: totalPages,
TxsOnPage: txsOnPage,
}
glog.Info(address, " finished in ", time.Since(start))
return r, nil
}
// GetAddress computes address value and gets transactions for given address
func (w *Worker) GetAddressOld(addrID string, page int, txsOnPage int) (*Address, error) {
glog.Info(addrID, " start")
txc, err := w.getAddressTxids(addrID, false)
txc = UniqueTxidsInReverse(txc)
if err != nil {
return nil, err
}
txm, err := w.getAddressTxids(addrID, true)
if err != nil {
return nil, err
}
2018-07-25 16:15:29 -06:00
txm = UniqueTxidsInReverse(txm)
bestheight, _, err := w.db.GetBestBlock()
if err != nil {
return nil, err
}
lc := len(txc)
if lc > txsOnPage {
lc = txsOnPage
}
txs := make([]*Tx, len(txm)+lc)
txi := 0
2018-07-24 07:58:37 -06:00
var uBalSat, balSat, totRecvSat, totSentSat big.Int
for _, tx := range txm {
tx, err := w.GetTransaction(tx, bestheight, false)
// mempool transaction may fail
if err != nil {
glog.Error("GetTransaction ", tx, ": ", err)
} else {
2018-07-24 07:58:37 -06:00
uBalSat.Sub(tx.getAddrVoutValue(addrID), tx.getAddrVinValue(addrID))
txs[txi] = tx
txi++
}
}
if page < 0 {
page = 0
}
from := page * txsOnPage
if from > len(txc) {
from = 0
}
to := from + txsOnPage
for i, tx := range txc {
tx, err := w.GetTransaction(tx, bestheight, false)
if err != nil {
return nil, err
} else {
2018-07-24 07:58:37 -06:00
totRecvSat.Add(&totRecvSat, tx.getAddrVoutValue(addrID))
totSentSat.Add(&totSentSat, tx.getAddrVinValue(addrID))
if i >= from && i < to {
txs[txi] = tx
txi++
}
}
}
2018-07-24 07:58:37 -06:00
balSat.Sub(&totRecvSat, &totSentSat)
r := &Address{
AddrStr: addrID,
2018-07-24 07:58:37 -06:00
Balance: w.chainParser.AmountToDecimalString(&balSat),
TotalReceived: w.chainParser.AmountToDecimalString(&totRecvSat),
TotalSent: w.chainParser.AmountToDecimalString(&totSentSat),
Transactions: txs[:txi],
TxApperances: len(txc),
2018-07-24 07:58:37 -06:00
UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat),
UnconfirmedTxApperances: len(txm),
}
glog.Info(addrID, " finished")
return r, nil
}