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-08-27 07:36:33 -06:00
|
|
|
"fmt"
|
2018-07-24 07:58:37 -06:00
|
|
|
"math/big"
|
2018-08-24 08:17:43 -06:00
|
|
|
"time"
|
2018-06-29 18:02:16 -06:00
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2018-08-24 08:17:43 -06:00
|
|
|
"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
|
2018-08-27 07:36:33 -06:00
|
|
|
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 {
|
2018-08-27 07:36:33 -06:00
|
|
|
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 {
|
2018-08-24 08:17:43 -06:00
|
|
|
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
|
2018-06-28 13:18:52 -06:00
|
|
|
// bchainVin.Txid=="" is coinbase transaction
|
|
|
|
if bchainVin.Txid != "" {
|
2018-08-27 07:36:33 -06:00
|
|
|
// load spending addresses from TxAddresses
|
|
|
|
ta, err := w.db.GetTxAddresses(bchainVin.Txid)
|
2018-06-28 13:18:52 -06:00
|
|
|
if err != nil {
|
2018-08-27 07:36:33 -06:00
|
|
|
return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid)
|
2018-06-28 13:18:52 -06:00
|
|
|
}
|
2018-08-27 07:36:33 -06:00
|
|
|
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-28 13:18:52 -06:00
|
|
|
}
|
2018-06-26 05:02:53 -06:00
|
|
|
}
|
2018-08-27 07:36:33 -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
|
2018-08-27 07:36:33 -06:00
|
|
|
if spendingTxs {
|
2018-06-26 05:02:53 -06:00
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
}
|
2018-06-28 13:18:52 -06:00
|
|
|
// 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-28 13:18:52 -06:00
|
|
|
}
|
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,
|
2018-08-27 07:36:33 -06:00
|
|
|
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
|
|
|
|
}
|
2018-06-29 18:02:16 -06:00
|
|
|
|
2018-08-27 07:36:33 -06:00
|
|
|
func (w *Worker) getAddressTxids(address string, mempool bool) ([]string, error) {
|
2018-06-29 18:02:16 -06:00
|
|
|
var err error
|
|
|
|
txids := make([]string, 0)
|
|
|
|
if !mempool {
|
2018-08-27 07:36:33 -06:00
|
|
|
err = w.db.GetTransactions(address, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
|
2018-06-29 18:02:16 -06:00
|
|
|
txids = append(txids, txid)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
2018-08-27 07:36:33 -06:00
|
|
|
m, err := w.chain.GetMempoolTransactions(address)
|
2018-06-29 18:02:16 -06:00
|
|
|
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
|
2018-06-29 18:02:16 -06:00
|
|
|
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-06-29 18:02:16 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-24 07:58:37 -06:00
|
|
|
return &val
|
2018-06-29 18:02:16 -06:00
|
|
|
}
|
|
|
|
|
2018-07-24 07:58:37 -06:00
|
|
|
func (t *Tx) getAddrVinValue(addrID string) *big.Int {
|
|
|
|
var val big.Int
|
2018-06-29 18:02:16 -06:00
|
|
|
for _, vin := range t.Vin {
|
|
|
|
if vin.Addr == addrID {
|
2018-07-24 07:58:37 -06:00
|
|
|
val.Add(&val, &vin.ValueSat)
|
2018-06-29 18:02:16 -06:00
|
|
|
}
|
|
|
|
}
|
2018-07-24 07:58:37 -06:00
|
|
|
return &val
|
2018-06-29 18:02:16 -06:00
|
|
|
}
|
|
|
|
|
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-08-21 16:48:53 -06:00
|
|
|
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 {
|
2018-08-21 16:48:53 -06:00
|
|
|
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 {
|
2018-08-21 16:48:53 -06:00
|
|
|
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,
|
2018-08-21 16:48:53 -06:00
|
|
|
Blockheight: int(ta.Height),
|
2018-08-23 15:20:07 -06:00
|
|
|
Blocktime: bi.Time,
|
2018-08-21 16:48:53 -06:00
|
|
|
Confirmations: bestheight - ta.Height + 1,
|
|
|
|
Fees: w.chainParser.AmountToDecimalString(&feesSat),
|
2018-08-23 15:20:07 -06:00
|
|
|
Time: bi.Time,
|
2018-08-21 16:48:53 -06:00
|
|
|
Txid: txid,
|
|
|
|
ValueIn: w.chainParser.AmountToDecimalString(&valInSat),
|
|
|
|
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
|
|
|
|
Vin: vins,
|
|
|
|
Vout: vouts,
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2018-06-29 18:02:16 -06:00
|
|
|
// GetAddress computes address value and gets transactions for given address
|
2018-08-24 08:17:43 -06:00
|
|
|
func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids bool) (*Address, error) {
|
|
|
|
start := time.Now()
|
2018-08-27 07:36:33 -06:00
|
|
|
if page < 0 {
|
|
|
|
page = 0
|
|
|
|
}
|
2018-08-21 02:11:27 -06:00
|
|
|
ba, err := w.db.GetAddressBalance(address)
|
|
|
|
if err != nil {
|
2018-08-27 07:36:33 -06:00
|
|
|
return nil, NewApiError(fmt.Sprintf("Address not found, %v", err), true)
|
2018-08-21 02:11:27 -06:00
|
|
|
}
|
|
|
|
if ba == nil {
|
2018-08-24 08:17:43 -06:00
|
|
|
return nil, NewApiError("Address not found", true)
|
2018-08-21 02:11:27 -06:00
|
|
|
}
|
|
|
|
txc, err := w.getAddressTxids(address, false)
|
|
|
|
if err != nil {
|
2018-08-24 08:17:43 -06:00
|
|
|
return nil, errors.Annotatef(err, "getAddressTxids %v false", address)
|
2018-08-21 02:11:27 -06:00
|
|
|
}
|
2018-08-24 08:17:43 -06:00
|
|
|
txc = UniqueTxidsInReverse(txc)
|
2018-08-27 07:36:33 -06:00
|
|
|
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)
|
2018-08-21 02:11:27 -06:00
|
|
|
}
|
|
|
|
bestheight, _, err := w.db.GetBestBlock()
|
|
|
|
if err != nil {
|
2018-08-24 08:17:43 -06:00
|
|
|
return nil, errors.Annotatef(err, "GetBestBlock")
|
2018-08-21 02:11:27 -06:00
|
|
|
}
|
|
|
|
// 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 {
|
2018-08-24 08:17:43 -06:00
|
|
|
glog.Error("GetTransaction in mempool ", tx, ": ", err)
|
2018-08-21 02:11:27 -06:00
|
|
|
} else {
|
|
|
|
uBalSat.Sub(tx.getAddrVoutValue(address), tx.getAddrVinValue(address))
|
|
|
|
txs[txi] = tx
|
|
|
|
txi++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(txc) != int(ba.Txs) {
|
2018-08-21 16:48:53 -06:00
|
|
|
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 {
|
2018-08-24 08:17:43 -06:00
|
|
|
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
|
2018-08-21 16:48:53 -06:00
|
|
|
}
|
|
|
|
if ta == nil {
|
|
|
|
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
bi, err := w.db.GetBlockInfo(ta.Height)
|
|
|
|
if err != nil {
|
2018-08-24 08:17:43 -06:00
|
|
|
return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
|
2018-08-21 16:48:53 -06:00
|
|
|
}
|
|
|
|
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++
|
2018-08-21 02:11:27 -06:00
|
|
|
}
|
|
|
|
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),
|
2018-08-21 16:48:53 -06:00
|
|
|
Transactions: txs[:txi],
|
|
|
|
Page: page,
|
|
|
|
TotalPages: totalPages,
|
|
|
|
TxsOnPage: txsOnPage,
|
2018-08-21 02:11:27 -06:00
|
|
|
}
|
2018-08-24 08:17:43 -06:00
|
|
|
glog.Info(address, " finished in ", time.Since(start))
|
2018-08-21 02:11:27 -06:00
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAddress computes address value and gets transactions for given address
|
2018-08-21 16:48:53 -06:00
|
|
|
func (w *Worker) GetAddressOld(addrID string, page int, txsOnPage 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
|
|
|
|
}
|
2018-07-25 16:15:29 -06:00
|
|
|
txm = UniqueTxidsInReverse(txm)
|
2018-06-29 18:02:16 -06:00
|
|
|
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
|
2018-07-24 07:58:37 -06:00
|
|
|
var uBalSat, balSat, totRecvSat, totSentSat big.Int
|
2018-06-29 18:02:16 -06:00
|
|
|
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))
|
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 {
|
2018-07-24 07:58:37 -06:00
|
|
|
totRecvSat.Add(&totRecvSat, tx.getAddrVoutValue(addrID))
|
|
|
|
totSentSat.Add(&totSentSat, 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
|
|
|
}
|
|
|
|
}
|
2018-07-24 07:58:37 -06:00
|
|
|
balSat.Sub(&totRecvSat, &totSentSat)
|
2018-06-29 18:02:16 -06:00
|
|
|
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),
|
2018-06-29 18:02:16 -06:00
|
|
|
Transactions: txs[:txi],
|
|
|
|
TxApperances: len(txc),
|
2018-07-24 07:58:37 -06:00
|
|
|
UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat),
|
2018-06-29 18:02:16 -06:00
|
|
|
UnconfirmedTxApperances: len(txm),
|
|
|
|
}
|
|
|
|
glog.Info(addrID, " finished")
|
|
|
|
return r, nil
|
|
|
|
}
|