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"
|
2018-06-29 18:02:16 -06:00
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2018-06-26 05:02:53 -06:00
|
|
|
)
|
|
|
|
|
2018-07-20 11:07:43 -06:00
|
|
|
const txsOnPage = 30
|
|
|
|
|
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
|
2018-06-26 05:02:53 -06:00
|
|
|
func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) (*Tx, error) {
|
|
|
|
bchainTx, height, err := w.txCache.GetTransaction(txid, bestheight)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var blockhash string
|
|
|
|
if bchainTx.Confirmations > 0 {
|
|
|
|
blockhash, err = w.db.GetBlockHash(height)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var valIn, valOut, fees float64
|
|
|
|
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
|
2018-06-28 13:18:52 -06:00
|
|
|
// bchainVin.Txid=="" is coinbase transaction
|
|
|
|
if bchainVin.Txid != "" {
|
|
|
|
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(otx.Vout) > int(vin.Vout) {
|
|
|
|
vout := &otx.Vout[vin.Vout]
|
|
|
|
vin.Value = vout.Value
|
|
|
|
valIn += vout.Value
|
|
|
|
vin.ValueSat = int64(vout.Value*1E8 + 0.5)
|
|
|
|
if vout.Address != nil {
|
|
|
|
a := vout.Address.String()
|
|
|
|
vin.Addr = a
|
|
|
|
}
|
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
|
|
|
|
vout.Value = bchainVout.Value
|
|
|
|
valOut += bchainVout.Value
|
|
|
|
vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex
|
|
|
|
vout.ScriptPubKey.Addresses = bchainVout.ScriptPubKey.Addresses
|
|
|
|
if spendingTx {
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
}
|
2018-06-28 13:18:52 -06:00
|
|
|
// for coinbase transactions valIn is 0
|
|
|
|
fees = valIn - valOut
|
|
|
|
if fees < 0 {
|
|
|
|
fees = 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,
|
|
|
|
Fees: fees,
|
|
|
|
Locktime: bchainTx.LockTime,
|
2018-06-27 16:36:56 -06:00
|
|
|
WithSpends: spendingTx,
|
2018-06-26 05:02:53 -06:00
|
|
|
Time: bchainTx.Time,
|
2018-06-28 14:41:46 -06:00
|
|
|
Txid: bchainTx.Txid,
|
2018-06-26 05:02:53 -06:00
|
|
|
ValueIn: valIn,
|
|
|
|
ValueOut: valOut,
|
|
|
|
Version: bchainTx.Version,
|
|
|
|
Vin: vins,
|
|
|
|
Vout: vouts,
|
|
|
|
}
|
|
|
|
return r, nil
|
|
|
|
}
|
2018-06-29 18:02:16 -06:00
|
|
|
|
|
|
|
func (s *Worker) getAddressTxids(address string, mempool bool) ([]string, error) {
|
|
|
|
var err error
|
|
|
|
txids := make([]string, 0)
|
|
|
|
if !mempool {
|
|
|
|
err = s.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 := s.chain.GetMempoolTransactions(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txids = append(txids, m...)
|
|
|
|
}
|
|
|
|
return txids, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tx) getAddrVoutValue(addrID string) float64 {
|
|
|
|
var val float64
|
|
|
|
for _, vout := range t.Vout {
|
|
|
|
for _, a := range vout.ScriptPubKey.Addresses {
|
|
|
|
if a == addrID {
|
|
|
|
val += vout.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tx) getAddrVinValue(addrID string) float64 {
|
|
|
|
var val float64
|
|
|
|
for _, vin := range t.Vin {
|
|
|
|
if vin.Addr == addrID {
|
|
|
|
val += vin.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
|
2018-07-20 11:07:43 -06:00
|
|
|
// 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:]
|
|
|
|
}
|
|
|
|
|
2018-06-29 18:02:16 -06:00
|
|
|
// GetAddress computes address value and gets transactions for given address
|
2018-07-20 11:07:43 -06:00
|
|
|
func (w *Worker) GetAddress(addrID string, page int) (*Address, error) {
|
2018-06-29 18:02:16 -06:00
|
|
|
glog.Info(addrID, " start")
|
|
|
|
txc, err := w.getAddressTxids(addrID, false)
|
2018-07-20 11:07:43 -06:00
|
|
|
txc = UniqueTxidsInReverse(txc)
|
2018-06-29 18:02:16 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txm, err := w.getAddressTxids(addrID, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
bestheight, _, err := w.db.GetBestBlock()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-20 11:07:43 -06:00
|
|
|
lc := len(txc)
|
|
|
|
if lc > txsOnPage {
|
|
|
|
lc = txsOnPage
|
|
|
|
}
|
|
|
|
txs := make([]*Tx, len(txm)+lc)
|
2018-06-29 18:02:16 -06:00
|
|
|
txi := 0
|
|
|
|
var uBal, bal, totRecv, totSent float64
|
|
|
|
for _, tx := range txm {
|
|
|
|
tx, err := w.GetTransaction(tx, bestheight, false)
|
|
|
|
// mempool transaction may fail
|
|
|
|
if err != nil {
|
|
|
|
glog.Error("GetTransaction ", tx, ": ", err)
|
|
|
|
} else {
|
|
|
|
uBal = tx.getAddrVoutValue(addrID) - tx.getAddrVinValue(addrID)
|
2018-07-20 11:07:43 -06:00
|
|
|
txs[txi] = tx
|
2018-06-29 18:02:16 -06:00
|
|
|
txi++
|
|
|
|
}
|
|
|
|
}
|
2018-07-20 11:07:43 -06:00
|
|
|
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)
|
2018-06-29 18:02:16 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
totRecv += tx.getAddrVoutValue(addrID)
|
|
|
|
totSent += tx.getAddrVinValue(addrID)
|
2018-07-20 11:07:43 -06:00
|
|
|
if i >= from && i < to {
|
|
|
|
txs[txi] = tx
|
|
|
|
txi++
|
|
|
|
}
|
2018-06-29 18:02:16 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
bal = totRecv - totSent
|
|
|
|
r := &Address{
|
|
|
|
AddrStr: addrID,
|
|
|
|
Balance: bal,
|
|
|
|
BalanceSat: int64(bal*1E8 + 0.5),
|
|
|
|
TotalReceived: totRecv,
|
|
|
|
TotalReceivedSat: int64(totRecv*1E8 + 0.5),
|
|
|
|
TotalSent: totSent,
|
|
|
|
TotalSentSat: int64(totSent*1E8 + 0.5),
|
|
|
|
Transactions: txs[:txi],
|
|
|
|
TxApperances: len(txc),
|
|
|
|
UnconfirmedBalance: uBal,
|
|
|
|
UnconfirmedTxApperances: len(txm),
|
|
|
|
}
|
|
|
|
glog.Info(addrID, " finished")
|
|
|
|
return r, nil
|
|
|
|
}
|