Limit number of found transactions for address by page size

This commit is contained in:
Martin Boehm 2019-01-09 11:48:19 +01:00
parent 2c3af5129e
commit 00c1fd6661
4 changed files with 111 additions and 64 deletions

View File

@ -9,6 +9,8 @@ import (
"time" "time"
) )
const maxInt = int(^uint(0) >> 1)
// GetAddressOption specifies what data returns GetAddress api call // GetAddressOption specifies what data returns GetAddress api call
type GetAddressOption int type GetAddressOption int

View File

@ -300,13 +300,16 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
return r, nil return r, nil
} }
func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter *AddressFilter) ([]string, error) { func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter *AddressFilter, maxResults int) ([]string, error) {
var err error var err error
txids := make([]string, 0, 4) txids := make([]string, 0, 4)
var callback db.GetTransactionsCallback var callback db.GetTransactionsCallback
if filter.Vout == AddressFilterVoutOff { if filter.Vout == AddressFilterVoutOff {
callback = func(txid string, height uint32, indexes []int32) error { callback = func(txid string, height uint32, indexes []int32) error {
txids = append(txids, txid) txids = append(txids, txid)
if len(txids) >= maxResults {
return &db.StopIteration{}
}
return nil return nil
} }
} else { } else {
@ -320,6 +323,9 @@ func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool
(filter.Vout == AddressFilterVoutOutputs && index >= 0) || (filter.Vout == AddressFilterVoutOutputs && index >= 0) ||
(vout == int32(filter.Vout)) { (vout == int32(filter.Vout)) {
txids = append(txids, txid) txids = append(txids, txid)
if len(txids) >= maxResults {
return &db.StopIteration{}
}
break break
} }
} }
@ -460,16 +466,19 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
}, from, to, page }, from, to, page
} }
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, error) { func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) {
var ( var (
ba *db.AddrBalance ba *db.AddrBalance
tokens []Token tokens []Token
ci *bchain.Erc20Contract ci *bchain.Erc20Contract
n uint64 n uint64
nonContractTxs int
) )
// unknown number of results for paging
totalResults := -1
ca, err := w.db.GetAddrDescContracts(addrDesc) ca, err := w.db.GetAddrDescContracts(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
} }
if ca != nil { if ca != nil {
ba = &db.AddrBalance{ ba = &db.AddrBalance{
@ -478,20 +487,20 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
var b *big.Int var b *big.Int
b, err = w.chain.EthereumTypeGetBalance(addrDesc) b, err = w.chain.EthereumTypeGetBalance(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc) return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc)
} }
if b != nil { if b != nil {
ba.BalanceSat = *b ba.BalanceSat = *b
} }
n, err = w.chain.EthereumTypeGetNonce(addrDesc) n, err = w.chain.EthereumTypeGetNonce(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc) return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc)
} }
var filterDesc bchain.AddressDescriptor var filterDesc bchain.AddressDescriptor
if filter.Contract != "" { if filter.Contract != "" {
filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract) filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract)
if err != nil { if err != nil {
return nil, nil, nil, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true) return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
} }
} }
tokens = make([]Token, len(ca.Contracts)) tokens = make([]Token, len(ca.Contracts))
@ -501,12 +510,12 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
if !bytes.Equal(filterDesc, c.Contract) { if !bytes.Equal(filterDesc, c.Contract) {
continue continue
} }
// filter only transactions by this contract // filter only transactions of this contract
filter.Vout = i + 1 filter.Vout = i + 1
} }
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract) ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
if err != nil { if err != nil {
return nil, nil, nil, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract) return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
} }
if ci == nil { if ci == nil {
ci = &bchain.Erc20Contract{} ci = &bchain.Erc20Contract{}
@ -541,10 +550,21 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
tokens = tokens[:j] tokens = tokens[:j]
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc) ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, 0, err return nil, nil, nil, 0, 0, 0, err
} }
if filter.FromHeight == 0 && filter.ToHeight == 0 {
// compute total results for paging
if filter.Vout == AddressFilterVoutOff {
totalResults = int(ca.TotalTxs)
} else if filter.Vout == 0 {
totalResults = int(ca.NonContractTxs)
} else if filter.Vout > 0 && filter.Vout-1 < len(ca.Contracts) {
totalResults = int(ca.Contracts[filter.Vout-1].Txs)
}
}
nonContractTxs = int(ca.NonContractTxs)
} }
return ba, tokens, ci, n, int(ca.NonContractTxs), nil return ba, tokens, ci, n, nonContractTxs, totalResults, nil
} }
// GetAddress computes address value and gets transactions for given address // GetAddress computes address value and gets transactions for given address
@ -570,10 +590,11 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
totalReceived, totalSent *big.Int totalReceived, totalSent *big.Int
nonce string nonce string
nonTokenTxs int nonTokenTxs int
totalResults int
) )
if w.chainType == bchain.ChainEthereumType { if w.chainType == bchain.ChainEthereumType {
var n uint64 var n uint64
ba, tokens, erc20c, n, nonTokenTxs, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter) ba, tokens, erc20c, n, nonTokenTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -584,6 +605,14 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
if err != nil { if err != nil {
return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
} }
if ba != nil {
// totalResults is known only if there is no filter
if filter.Vout == AddressFilterVoutOff && filter.FromHeight == 0 && filter.ToHeight == 0 {
totalResults = int(ba.Txs)
} else {
totalResults = -1
}
}
} }
// get tx history if requested by option or check mempool if there are some transactions for a new address // get tx history if requested by option or check mempool if there are some transactions for a new address
if option >= TxidHistory || ba == nil { if option >= TxidHistory || ba == nil {
@ -595,9 +624,12 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
if len(addresses) == 1 { if len(addresses) == 1 {
address = addresses[0] address = addresses[0]
} }
txm, err = w.getAddressTxids(addrDesc, true, filter) // get txs from mempool only if blockheight filter is off
if err != nil { if filter.FromHeight == 0 && filter.ToHeight == 0 {
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc) txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
}
} }
// if there are only unconfirmed transactions, there is no paging // if there are only unconfirmed transactions, there is no paging
if ba == nil { if ba == nil {
@ -605,7 +637,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
page = 0 page = 0
} }
if option >= TxidHistory { if option >= TxidHistory {
txc, err := w.getAddressTxids(addrDesc, false, filter) txc, err := w.getAddressTxids(addrDesc, false, filter, (page+1)*txsOnPage)
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc) return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
} }
@ -615,6 +647,13 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
} }
var from, to int var from, to int
pg, from, to, page = computePaging(len(txc), page, txsOnPage) pg, from, to, page = computePaging(len(txc), page, txsOnPage)
if len(txc) >= txsOnPage {
if totalResults < 0 {
pg.TotalPages = -1
} else {
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
}
}
if option == TxidHistory { if option == TxidHistory {
txids = make([]string, len(txm)+to-from) txids = make([]string, len(txm)+to-from)
} else { } else {
@ -654,7 +693,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
} }
if ta == nil { if ta == nil {
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
continue // as fallback, provide empty TxAddresses to return at least something
ta = &db.TxAddresses{}
} }
bi, err := w.db.GetBlockInfo(ta.Height) bi, err := w.db.GetBlockInfo(ta.Height)
if err != nil { if err != nil {
@ -662,7 +702,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
} }
if bi == nil { if bi == nil {
glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db") glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db")
continue // provide empty BlockInfo to return the rest of tx data
bi = &db.BlockInfo{}
} }
txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight) txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight)
} else { } else {
@ -716,7 +757,7 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
r := make([]AddressUtxo, 0, 8) r := make([]AddressUtxo, 0, 8)
if !onlyConfirmed { if !onlyConfirmed {
// get utxo from mempool // get utxo from mempool
txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}) txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt)
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", address) return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
} }

View File

@ -640,55 +640,59 @@ func (s *PublicServer) explorerSendTx(w http.ResponseWriter, r *http.Request) (t
} }
func getPagingRange(page int, total int) ([]int, int, int) { func getPagingRange(page int, total int) ([]int, int, int) {
if total < 2 { // total==-1 means total is unknown, show only prev/next buttons
if total >= 0 && total < 2 {
return nil, 0, 0 return nil, 0, 0
} }
var r []int
pp, np := page-1, page+1 pp, np := page-1, page+1
if np > total {
np = total
}
if pp < 1 { if pp < 1 {
pp = 1 pp = 1
} }
r := make([]int, 0, 8) if total > 0 {
if total < 6 { if np > total {
for i := 1; i <= total; i++ { np = total
r = append(r, i)
} }
} else { r = make([]int, 0, 8)
r = append(r, 1) if total < 6 {
if page > 3 { for i := 1; i <= total; i++ {
r = append(r, 0) r = append(r, i)
}
if pp == 1 {
if page == 1 {
r = append(r, np)
r = append(r, np+1)
r = append(r, np+2)
} else {
r = append(r, page)
r = append(r, np)
r = append(r, np+1)
}
} else if np == total {
if page == total {
r = append(r, pp-2)
r = append(r, pp-1)
r = append(r, pp)
} else {
r = append(r, pp-1)
r = append(r, pp)
r = append(r, page)
} }
} else { } else {
r = append(r, pp) r = append(r, 1)
r = append(r, page) if page > 3 {
r = append(r, np) r = append(r, 0)
}
if pp == 1 {
if page == 1 {
r = append(r, np)
r = append(r, np+1)
r = append(r, np+2)
} else {
r = append(r, page)
r = append(r, np)
r = append(r, np+1)
}
} else if np == total {
if page == total {
r = append(r, pp-2)
r = append(r, pp-1)
r = append(r, pp)
} else {
r = append(r, pp-1)
r = append(r, pp)
r = append(r, page)
}
} else {
r = append(r, pp)
r = append(r, page)
r = append(r, np)
}
if page <= total-3 {
r = append(r, 0)
}
r = append(r, total)
} }
if page <= total-3 {
r = append(r, 0)
}
r = append(r, total)
} }
return r, pp, np return r, pp, np
} }

View File

@ -1,11 +1,11 @@
{{- define "paging"}}{{$data := . -}}{{if $data.PagingRange -}} {{- define "paging"}}{{$data := .}}{{if $data.PrevPage -}}
<ul class="pagination justify-content-end"> <ul class="pagination justify-content-end">
<li class="page-item"><a class="page-link" href="?page={{$data.PrevPage}}{{$data.PageParams}}">&lt;</a></li> <li class="page-item"><a class="page-link" href="?page={{$data.PrevPage}}{{$data.PageParams}}">&lt;</a></li>
{{- range $p := $data.PagingRange -}} {{- range $p := $data.PagingRange -}}
<li class="page-item{{if eq $data.Page $p}} active{{end}}"> <li class="page-item{{if eq $data.Page $p}} active{{end}}">
{{- if $p}}<a class="page-link" href="?page={{$p}}{{$data.PageParams}}">{{$p}}</a> {{- if $p}}<a class="page-link" href="?page={{$p}}{{$data.PageParams}}">{{$p}}</a>
{{- else -}}<span class="page-text">...</span>{{- end -}} {{- else}}<span class="page-text">...</span>{{end -}}
</li>{{- end -}} </li>{{- end -}}
<li class="page-item"><a class="page-link" href="?page={{$data.NextPage}}{{$data.PageParams}}">&gt;</a></li> <li class="page-item"><a class="page-link" href="?page={{$data.NextPage}}{{$data.PageParams}}">&gt;</a></li>
</ul> </ul>
{{- end -}}{{- end -}} {{- end}}{{end -}}