Unify AccountDetails levels in GetAccount and GetXpub api calls

xpub
Martin Boehm 2019-02-28 15:07:07 +01:00
parent 46001a9fa5
commit 7b590d9958
6 changed files with 99 additions and 90 deletions

View File

@ -14,20 +14,22 @@ const maxUint32 = ^uint32(0)
const maxInt = int(^uint(0) >> 1)
const maxInt64 = int64(^uint64(0) >> 1)
// GetAddressOption specifies what data returns GetAddress api call
type GetAddressOption int
// AccountDetails specifies what data returns GetAddress and GetXpub calls
type AccountDetails int
const (
// Basic - only that address is indexed and some basic info
Basic GetAddressOption = iota
// Tokens - basic info + tokens
Tokens
// TxidHistory - basic + tokens + txids, subject to paging
TxidHistory
// TxHistoryLight - basic + tokens + easily obtained tx data (not requiring request to backend), subject to paging
TxHistoryLight
// TxHistory - basic + tokens + full tx data, subject to paging
TxHistory
// AccountDetailsBasic - only that address is indexed and some basic info
AccountDetailsBasic AccountDetails = iota
// AccountDetailsTokens - basic info + tokens
AccountDetailsTokens
// AccountDetailsTokenBalances - basic info + token with balance
AccountDetailsTokenBalances
// AccountDetailsTxidHistory - basic + token balances + txids, subject to paging
AccountDetailsTxidHistory
// AccountDetailsTxHistoryLight - basic + tokens + easily obtained tx data (not requiring requests to backend), subject to paging
AccountDetailsTxHistoryLight
// AccountDetailsTxHistory - basic + tokens + full tx data, subject to paging
AccountDetailsTxHistory
)
// ErrUnsupportedXpub is returned when coin type does not support xpub address derivation or provided string is not an xpub

View File

@ -479,7 +479,7 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
}, from, to, page
}
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) {
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) {
var (
ba *db.AddrBalance
tokens []Token
@ -516,53 +516,55 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
}
}
tokens = make([]Token, len(ca.Contracts))
var j int
for i, c := range ca.Contracts {
if len(filterDesc) > 0 {
if !bytes.Equal(filterDesc, c.Contract) {
continue
if details > AccountDetailsBasic {
tokens = make([]Token, len(ca.Contracts))
var j int
for i, c := range ca.Contracts {
if len(filterDesc) > 0 {
if !bytes.Equal(filterDesc, c.Contract) {
continue
}
// filter only transactions of this contract
filter.Vout = i + 1
}
// filter only transactions of this contract
filter.Vout = i + 1
}
validContract := true
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
if err != nil {
return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
}
if ci == nil {
ci = &bchain.Erc20Contract{}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
validContract = false
}
// do not read contract balances etc in case of Basic option
if option != Basic && validContract {
b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
validContract := true
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
if err != nil {
// return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err)
return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
}
} else {
b = nil
if ci == nil {
ci = &bchain.Erc20Contract{}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
validContract = false
}
// do not read contract balances etc in case of Basic option
if details >= AccountDetailsTokenBalances && validContract {
b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
if err != nil {
// return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err)
}
} else {
b = nil
}
tokens[j] = Token{
Type: ERC20TokenType,
BalanceSat: (*Amount)(b),
Contract: ci.Contract,
Name: ci.Name,
Symbol: ci.Symbol,
Transfers: int(c.Txs),
Decimals: ci.Decimals,
ContractIndex: strconv.Itoa(i + 1),
}
j++
}
tokens[j] = Token{
Type: ERC20TokenType,
BalanceSat: (*Amount)(b),
Contract: ci.Contract,
Name: ci.Name,
Symbol: ci.Symbol,
Transfers: int(c.Txs),
Decimals: ci.Decimals,
ContractIndex: strconv.Itoa(i + 1),
}
j++
tokens = tokens[:j]
}
tokens = tokens[:j]
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
if err != nil {
return nil, nil, nil, 0, 0, 0, err
@ -582,11 +584,11 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
return ba, tokens, ci, n, nonContractTxs, totalResults, nil
}
func (w *Worker) txFromTxid(txid string, bestheight uint32, option GetAddressOption) (*Tx, error) {
func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails) (*Tx, error) {
var tx *Tx
var err error
// only ChainBitcoinType supports TxHistoryLight
if option == TxHistoryLight && w.chainType == bchain.ChainBitcoinType {
if option == AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType {
ta, err := w.db.GetTxAddresses(txid)
if err != nil {
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
@ -632,7 +634,7 @@ func (w *Worker) getAddrDescAndNormalizeAddress(address string) (bchain.AddressD
}
// GetAddress computes address value and gets transactions for given address
func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetAddressOption, filter *AddressFilter) (*Address, error) {
func (w *Worker) GetAddress(address string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter) (*Address, error) {
start := time.Now()
page--
if page < 0 {
@ -700,9 +702,9 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
if page == 0 {
if option == TxidHistory {
if option == AccountDetailsTxidHistory {
txids = append(txids, tx.Txid)
} else if option >= TxHistoryLight {
} else if option >= AccountDetailsTxHistoryLight {
txs = append(txs, tx)
}
}
@ -711,7 +713,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
}
}
// get tx history if requested by option or check mempool if there are some transactions for a new address
if option >= TxidHistory {
if option >= AccountDetailsTxidHistory {
txc, err := w.getAddressTxids(addrDesc, false, filter, (page+1)*txsOnPage)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
@ -731,7 +733,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
}
for i := from; i < to; i++ {
txid := txc[i]
if option == TxidHistory {
if option == AccountDetailsTxidHistory {
txids = append(txids, txid)
} else {
tx, err := w.txFromTxid(txid, bestheight, option)

View File

@ -212,7 +212,7 @@ func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpub
return lastUsed, addresses, nil
}
func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeIndex int, index int) Token {
func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeIndex int, index int, option AccountDetails) Token {
a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc)
var address string
if len(a) > 0 {
@ -221,10 +221,12 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd
var balance, totalReceived, totalSent *big.Int
var transfers int
if ad.balance != nil {
balance = &ad.balance.BalanceSat
totalSent = &ad.balance.SentSat
totalReceived = ad.balance.ReceivedSat()
transfers = int(ad.balance.Txs)
if option >= AccountDetailsTokenBalances {
balance = &ad.balance.BalanceSat
totalSent = &ad.balance.SentSat
totalReceived = ad.balance.ReceivedSat()
}
}
return Token{
Type: XPUBAddressTokenType,
@ -260,7 +262,7 @@ func evictXpubCacheItems() {
glog.Info("Evicted ", count, " items from xpub cache, oldest item accessed at ", time.Unix(oldest, 0), ", cache size ", len(cachedXpubs))
}
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option GetAddressOption, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
if w.chainType != bchain.ChainBitcoinType || len(xpub) != xpubLen {
return nil, 0, ErrUnsupportedXpub
}
@ -325,7 +327,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option GetAdd
return nil, 0, err
}
}
if option >= TxidHistory {
if option >= AccountDetailsTxidHistory {
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil {
@ -346,7 +348,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option GetAdd
}
// GetXpubAddress computes address value and gets transactions for given address
func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option GetAddressOption, filter *AddressFilter, gap int) (*Address, error) {
func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*Address, error) {
start := time.Now()
page--
if page < 0 {
@ -419,9 +421,9 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(ad.addrDesc))
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(ad.addrDesc))
if page == 0 && !foundTx && (useTxids == nil || useTxids(&txid, ad)) {
if option == TxidHistory {
if option == AccountDetailsTxidHistory {
txids = append(txids, tx.Txid)
} else if option >= TxHistoryLight {
} else if option >= AccountDetailsTxHistoryLight {
txs = append(txs, tx)
}
}
@ -431,7 +433,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
}
}
}
if option >= TxidHistory {
if option >= AccountDetailsTxidHistory {
txcMap := make(map[string]bool)
txc = make(xpubTxids, 0, 32)
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
@ -472,7 +474,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
// get confirmed transactions
for i := from; i < to; i++ {
xpubTxid := &txc[i]
if option == TxidHistory {
if option == AccountDetailsTxidHistory {
txids = append(txids, xpubTxid.txid)
} else {
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option)
@ -488,7 +490,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
totalTokens := 0
var tokens []Token
var xpubAddresses map[string]struct{}
if option != Basic {
if option > AccountDetailsBasic {
tokens = make([]Token, 0, 4)
xpubAddresses = make(map[string]struct{})
}
@ -498,8 +500,8 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
if ad.balance != nil {
totalTokens++
}
if option != Basic {
token := w.tokenFromXpubAddress(data, ad, ci, i)
if option > AccountDetailsBasic {
token := w.tokenFromXpubAddress(data, ad, ci, i, option)
if filter.TokenLevel == TokenDetailDiscovered ||
filter.TokenLevel == TokenDetailUsed && ad.balance != nil ||
filter.TokenLevel == TokenDetailNonzeroBalance && ad.balance != nil && !IsZeroBigInt(&ad.balance.BalanceSat) {
@ -533,7 +535,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
// GetXpubUtxo returns unspent outputs for given xpub
func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, error) {
start := time.Now()
data, _, err := w.getXpubData(xpub, 0, 1, Basic, &AddressFilter{
data, _, err := w.getXpubData(xpub, 0, 1, AccountDetailsBasic, &AddressFilter{
Vout: AddressFilterVoutOff,
OnlyConfirmed: onlyConfirmed,
}, gap)
@ -556,7 +558,7 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e
return nil, err
}
if len(utxos) > 0 {
t := w.tokenFromXpubAddress(data, ad, ci, i)
t := w.tokenFromXpubAddress(data, ad, ci, i, AccountDetailsTokens)
for j := range utxos {
a := &utxos[j]
a.Address = t.Name

View File

@ -590,7 +590,7 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
}
}
}
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, api.TxHistoryLight, &api.AddressFilter{Vout: fn})
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, api.AccountDetailsTxHistoryLight, &api.AddressFilter{Vout: fn})
if err != nil {
return errorTpl, nil, err
}
@ -607,7 +607,7 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
return addressTpl, data, nil
}
func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int, option api.GetAddressOption) (*api.Address, api.TokenDetailLevel, error) {
func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int, option api.AccountDetails) (*api.Address, api.TokenDetailLevel, error) {
var fn = api.AddressFilterVoutOff
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
if ec != nil {
@ -650,7 +650,7 @@ func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl
var err error
s.metrics.ExplorerViews.With(common.Labels{"action": "xpub"}).Inc()
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
address, tokenLevel, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsOnPage, api.TxHistoryLight)
address, tokenLevel, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsOnPage, api.AccountDetailsTxHistoryLight)
if err != nil {
return errorTpl, nil, err
}
@ -730,7 +730,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
var err error
s.metrics.ExplorerViews.With(common.Labels{"action": "search"}).Inc()
if len(q) > 0 {
address, err = s.api.GetXpubAddress(q, 0, 1, api.Basic, &api.AddressFilter{Vout: api.AddressFilterVoutOff}, 0)
address, err = s.api.GetXpubAddress(q, 0, 1, api.AccountDetailsBasic, &api.AddressFilter{Vout: api.AddressFilterVoutOff}, 0)
if err == nil {
http.Redirect(w, r, joinURL("/xpub/", address.AddrStr), 302)
return noTpl, nil, nil
@ -745,7 +745,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
return noTpl, nil, nil
}
address, err = s.api.GetAddress(q, 0, 1, api.Basic, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
address, err = s.api.GetAddress(q, 0, 1, api.AccountDetailsBasic, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
if err == nil {
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
return noTpl, nil, nil
@ -907,7 +907,7 @@ func (s *PublicServer) apiAddress(r *http.Request, apiVersion int) (interface{},
if ec != nil {
page = 0
}
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.TxidHistory, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.AccountDetailsTxidHistory, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
if err == nil && apiVersion == apiV1 {
return s.api.AddressToV1(address), nil
}
@ -920,7 +920,7 @@ func (s *PublicServer) apiXpub(r *http.Request, apiVersion int) (interface{}, er
var err error
s.metrics.ExplorerViews.With(common.Labels{"action": "api-xpub"}).Inc()
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
address, _, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsInAPI, api.TxidHistory)
address, _, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsInAPI, api.AccountDetailsTxidHistory)
if err == nil && apiVersion == apiV1 {
return s.api.AddressToV1(address), nil
}

View File

@ -347,16 +347,18 @@ func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) {
}
func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) {
var opt api.GetAddressOption
var opt api.AccountDetails
switch req.Details {
case "tokens":
opt = api.Tokens
opt = api.AccountDetailsTokens
case "tokenBalances":
opt = api.AccountDetailsTokenBalances
case "txids":
opt = api.TxidHistory
opt = api.AccountDetailsTxidHistory
case "txs":
opt = api.TxHistory
opt = api.AccountDetailsTxHistory
default:
opt = api.Basic
opt = api.AccountDetailsBasic
}
filter := api.AddressFilter{
FromHeight: uint32(req.FromHeight),

View File

@ -305,6 +305,7 @@
<select id="getAccountInfoDetails" style="width: 20%; margin-left: 5px;">
<option value="basic">Basic</option>
<option value="tokens">Tokens</option>
<option value="tokenBalances">TokenBalances</option>
<option value="txids">Txids</option>
<option value="txs">Transactions</option>
</select>