From 4846af9f60d0d5f8126e82a1c43a799a2c112d85 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Tue, 12 Feb 2019 15:15:10 +0100 Subject: [PATCH] Control token detail level returned by xpub api --- api/types.go | 42 +++++++++++++++--------- api/worker.go | 12 +++---- api/xpub.go | 43 +++++++++++++++---------- server/public.go | 65 ++++++++++++++++++++++---------------- server/websocket.go | 6 ++-- static/templates/xpub.html | 8 ++--- static/test-websocket.html | 2 +- 7 files changed, 105 insertions(+), 73 deletions(-) diff --git a/api/types.go b/api/types.go index 2efd46ae..0d1f1b4f 100644 --- a/api/types.go +++ b/api/types.go @@ -19,13 +19,13 @@ type GetAddressOption int const ( // Basic - only that address is indexed and some basic info Basic GetAddressOption = iota - // Balance - only balances - Balance - // TxidHistory - balances and txids, subject to paging + // Tokens - basic info + tokens + Tokens + // TxidHistory - basic + tokens + txids, subject to paging TxidHistory - // TxHistoryLight - balances and easily obtained tx data (not requiring request to backend), subject to paging + // TxHistoryLight - basic + tokens + easily obtained tx data (not requiring request to backend), subject to paging TxHistoryLight - // TxHistory - balances and full tx data, subject to paging + // TxHistory - basic + tokens + full tx data, subject to paging TxHistory ) @@ -138,14 +138,17 @@ const XPUBAddressTokenType TokenType = "XPUBAddress" // Token contains info about tokens held by an address type Token struct { - Type TokenType `json:"type"` - Contract string `json:"contract"` - Transfers int `json:"transfers"` - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals int `json:"decimals"` - BalanceSat *Amount `json:"balance,omitempty"` - ContractIndex string `json:"-"` + Type TokenType `json:"type"` + Name string `json:"name"` + Path string `json:"path,omitempty"` + Contract string `json:"contract,omitempty"` + Transfers int `json:"transfers"` + Symbol string `json:"symbol,omitempty"` + Decimals int `json:"decimals,omitempty"` + BalanceSat *Amount `json:"balance,omitempty"` + TotalReceivedSat *Amount `json:"totalReceived,omitempty"` + TotalSentSat *Amount `json:"totalSent,omitempty"` + ContractIndex string `json:"-"` } // TokenTransfer contains info about a token transfer done in a transaction @@ -198,6 +201,9 @@ type Paging struct { ItemsOnPage int `json:"itemsOnPage,omitempty"` } +// TokenDetailLevel specifies detail level of tokens returned by GetAddress and GetXpubAddress +type TokenDetailLevel int + const ( // AddressFilterVoutOff disables filtering of transactions by vout AddressFilterVoutOff = -1 @@ -205,6 +211,13 @@ const ( AddressFilterVoutInputs = -2 // AddressFilterVoutOutputs specifies that only txs where the address is as output are returned AddressFilterVoutOutputs = -3 + + // TokenDetailNonzeroBalance - use to return only tokens with nonzero balance + TokenDetailNonzeroBalance TokenDetailLevel = 0 + // TokenDetailUsed - use to return tokens with some transfers (even if they have zero balance now) + TokenDetailUsed TokenDetailLevel = 1 + // TokenDetailDiscovered - use to return all discovered tokens + TokenDetailDiscovered TokenDetailLevel = 2 ) // AddressFilter is used to filter data returned from GetAddress api method @@ -213,8 +226,7 @@ type AddressFilter struct { Contract string FromHeight uint32 ToHeight uint32 - // AllTokens set to true will include xpub addresses with zero balance - AllTokens bool + TokenLevel TokenDetailLevel // OnlyConfirmed set to true will ignore mempool transactions; mempool is also ignored if FromHeight/ToHeight filter is specified OnlyConfirmed bool } diff --git a/api/worker.go b/api/worker.go index 5e084f7e..80e19871 100644 --- a/api/worker.go +++ b/api/worker.go @@ -51,7 +51,7 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript // setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output // there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error { - err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, ^uint32(0), func(t string, height uint32, indexes []int32) error { + err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, maxUint32, func(t string, height uint32, indexes []int32) error { for _, index := range indexes { // take only inputs if index < 0 { @@ -364,7 +364,7 @@ func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool } else { to := filter.ToHeight if to == 0 { - to = ^uint32(0) + to = maxUint32 } err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback) if err != nil { @@ -683,8 +683,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA ba = &db.AddrBalance{} page = 0 } - // process mempool, only if blockheight filter is off - if filter.FromHeight == 0 && filter.ToHeight == 0 && !filter.OnlyConfirmed { + // process mempool, only if toHeight is not specified + if filter.ToHeight == 0 && !filter.OnlyConfirmed { txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt) if err != nil { return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc) @@ -825,7 +825,7 @@ func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrB // ba can be nil if the address is only in mempool! if ba != nil && !IsZeroBigInt(&ba.BalanceSat) { outpoints := make([]bchain.Outpoint, 0, 8) - err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, height uint32, indexes []int32) error { + err = w.db.GetAddrDescTransactions(addrDesc, 0, maxUint32, func(txid string, height uint32, indexes []int32) error { for _, index := range indexes { // take only outputs if index >= 0 { @@ -943,7 +943,7 @@ func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) { // if it's a number, must be less than int32 var hash string height, err := strconv.Atoi(bid) - if err == nil && height < int(^uint32(0)) { + if err == nil && height < int(maxUint32) { hash, err = w.db.GetBlockHash(uint32(height)) if err != nil { hash = bid diff --git a/api/xpub.go b/api/xpub.go index 4ae2d9d0..c0076275 100644 --- a/api/xpub.go +++ b/api/xpub.go @@ -218,19 +218,23 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd if len(a) > 0 { address = a[0] } - var balance *big.Int + 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) } return Token{ - Type: XPUBAddressTokenType, - Name: address, - Decimals: w.chainParser.AmountDecimals(), - BalanceSat: (*Amount)(balance), - Transfers: transfers, - Contract: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index), + Type: XPUBAddressTokenType, + Name: address, + Decimals: w.chainParser.AmountDecimals(), + BalanceSat: (*Amount)(balance), + TotalReceivedSat: (*Amount)(totalReceived), + TotalSentSat: (*Amount)(totalSent), + Transfers: transfers, + Path: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index), } } @@ -371,8 +375,8 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get } totalResults = -1 } - // process mempool, only if blockheight filter is off - if filter.FromHeight == 0 && filter.ToHeight == 0 && !filter.OnlyConfirmed { + // process mempool, only if ToHeight is not specified + if filter.ToHeight == 0 && !filter.OnlyConfirmed { txmMap = make(map[string]*Tx) for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} { for i := range da { @@ -442,19 +446,27 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get } } totalTokens := 0 - xpubAddresses := make(map[string]struct{}) - tokens := make([]Token, 0, 4) + var tokens []Token + var xpubAddresses map[string]struct{} + if option != Basic { + tokens = make([]Token, 0, 4) + xpubAddresses = make(map[string]struct{}) + } for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} { for i := range da { ad := &da[i] - token := w.tokenFromXpubAddress(data, ad, ci, i) if ad.balance != nil { totalTokens++ - if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) { + } + if option != Basic { + token := w.tokenFromXpubAddress(data, ad, ci, i) + if filter.TokenLevel == TokenDetailDiscovered || + filter.TokenLevel == TokenDetailUsed && ad.balance != nil || + filter.TokenLevel == TokenDetailNonzeroBalance && ad.balance != nil && !IsZeroBigInt(&ad.balance.BalanceSat) { tokens = append(tokens, token) } + xpubAddresses[token.Name] = struct{}{} } - xpubAddresses[token.Name] = struct{}{} } } var totalReceived big.Int @@ -483,7 +495,6 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e start := time.Now() data, _, err := w.getXpubData(xpub, 0, 1, Basic, &AddressFilter{ Vout: AddressFilterVoutOff, - AllTokens: false, OnlyConfirmed: onlyConfirmed, }, gap) if err != nil { @@ -509,7 +520,7 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e for j := range utxos { a := &utxos[j] a.Address = t.Name - a.Path = t.Contract + a.Path = t.Path } r = append(r, utxos...) } diff --git a/server/public.go b/server/public.go index 674e9820..7b507c02 100644 --- a/server/public.go +++ b/server/public.go @@ -385,27 +385,27 @@ const ( // TemplateData is used to transfer data to the templates type TemplateData struct { - CoinName string - CoinShortcut string - CoinLabel string - InternalExplorer bool - ChainType bchain.ChainType - Address *api.Address - AddrStr string - Tx *api.Tx - Error *api.APIError - Blocks *api.Blocks - Block *api.Block - Info *api.SystemInfo - Page int - PrevPage int - NextPage int - PagingRange []int - PageParams template.URL - TOSLink string - SendTxHex string - Status string - AllTokens bool + CoinName string + CoinShortcut string + CoinLabel string + InternalExplorer bool + ChainType bchain.ChainType + Address *api.Address + AddrStr string + Tx *api.Tx + Error *api.APIError + Blocks *api.Blocks + Block *api.Block + Info *api.SystemInfo + Page int + PrevPage int + NextPage int + PagingRange []int + PageParams template.URL + TOSLink string + SendTxHex string + Status string + NonZeroBalanceTokens bool } func (s *PublicServer) parseTemplates() []*template.Template { @@ -600,7 +600,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, error) { +func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int, option api.GetAddressOption) (*api.Address, api.TokenDetailLevel, error) { var fn = api.AddressFilterVoutOff page, ec := strconv.Atoi(r.URL.Query().Get("page")) if ec != nil { @@ -624,16 +624,26 @@ func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int if ec != nil { gap = 0 } - allAddresses, _ := strconv.ParseBool(r.URL.Query().Get("alladdresses")) - return s.api.GetXpubAddress(xpub, page, pageSize, option, &api.AddressFilter{Vout: fn, AllTokens: allAddresses}, gap) + tokenLevel := api.TokenDetailNonzeroBalance + switch r.URL.Query().Get("tokenlevel") { + case "discovered": + tokenLevel = api.TokenDetailDiscovered + case "used": + tokenLevel = api.TokenDetailUsed + case "nonzero": + tokenLevel = api.TokenDetailNonzeroBalance + } + a, err := s.api.GetXpubAddress(xpub, page, pageSize, option, &api.AddressFilter{Vout: fn, TokenLevel: tokenLevel}, gap) + return a, tokenLevel, err } func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { var address *api.Address + var tokenLevel api.TokenDetailLevel var err error s.metrics.ExplorerViews.With(common.Labels{"action": "xpub"}).Inc() if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { - address, 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.TxHistoryLight) if err != nil { return errorTpl, nil, err } @@ -648,8 +658,7 @@ func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl data.PageParams = template.URL("&filter=" + filter) data.Address.Filter = filter } - allAddresses := r.URL.Query().Get("alladdresses") - data.AllTokens, _ = strconv.ParseBool(allAddresses) + data.NonZeroBalanceTokens = tokenLevel == api.TokenDetailNonzeroBalance return xpubTpl, data, nil } @@ -904,7 +913,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.TxidHistory) if err == nil && apiVersion == apiV1 { return s.api.AddressToV1(address), nil } diff --git a/server/websocket.go b/server/websocket.go index 5377910b..aa1d746e 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -349,8 +349,8 @@ func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) { func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) { var opt api.GetAddressOption switch req.Details { - case "balance": - opt = api.Balance + case "tokens": + opt = api.Tokens case "txids": opt = api.TxidHistory case "txs": @@ -363,7 +363,7 @@ func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, ToHeight: uint32(req.ToHeight), Contract: req.ContractFilter, Vout: api.AddressFilterVoutOff, - AllTokens: true, + TokenLevel: api.TokenDetailDiscovered, } a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, 0) if err != nil { diff --git a/static/templates/xpub.html b/static/templates/xpub.html index 743f5ff4..7ee8f429 100644 --- a/static/templates/xpub.html +++ b/static/templates/xpub.html @@ -31,7 +31,7 @@ {{- if $addr.TotalTokens -}} - {{if $data.AllTokens}}XPUB Addresses{{else}}XPUB Addresses with Balance{{end}} + {{if $data.NonZeroBalanceTokens}}XPUB Addresses with Balance{{else}}XPUB Addresses{{end}} @@ -46,12 +46,12 @@ - + {{- end -}} - {{- if not $data.AllTokens -}} + {{- if $data.NonZeroBalanceTokens -}} - + {{- end -}} diff --git a/static/test-websocket.html b/static/test-websocket.html index c5cca40d..ed28c05f 100644 --- a/static/test-websocket.html +++ b/static/test-websocket.html @@ -304,7 +304,7 @@
{{$t.Name}} {{formatAmount $t.BalanceSat}} {{$cs}} {{$t.Transfers}}{{$t.Contract}}{{$t.Path}}
Show all XPUB addressesShow all XPUB addresses