diff --git a/cmd/dcrdata/internal/explorer/explorerroutes.go b/cmd/dcrdata/internal/explorer/explorerroutes.go index dfa2817a0..41d351d8d 100644 --- a/cmd/dcrdata/internal/explorer/explorerroutes.go +++ b/cmd/dcrdata/internal/explorer/explorerroutes.go @@ -1,11 +1,10 @@ -// Copyright (c) 2018-2024, The Decred developers +// Copyright (c) 2018-2025, The Decred developers // Copyright (c) 2017, The dcrdata developers // See LICENSE for details. package explorer import ( - "context" "encoding/hex" "encoding/json" "errors" @@ -1389,12 +1388,6 @@ type TreasuryInfo struct { Path string Limit, Offset int64 // ?n=Limit&start=Offset TxnType string // ?txntype=TxnType - - // TODO: tadd and tspend can be unconfirmed. tspend for a very long time. - // NumUnconfirmed is the number of unconfirmed txns - // NumUnconfirmed int64 - // UnconfirmedTxns []*dbtypes.TreasuryTx - // Transactions on the current page Transactions []*dbtypes.TreasuryTx NumTransactions int64 // len(Transactions) but int64 for dumb template @@ -1402,49 +1395,31 @@ type TreasuryInfo struct { Balance *dbtypes.TreasuryBalance ConvertedBalance *exchanges.Conversion TypeCount int64 + + // tadd and tspend can be unconfirmed. tspend for a very long time. + Mempool *TreasuryMempoolInfo +} + +// TreasuryMempoolInfo holds the treasury-related mempool transactions that are +// not yet confirmed in a block. It is used to display treasury mempool +// information on the treasury page. +type TreasuryMempoolInfo struct { + NumTSpends int + NumTAdds int + TSpends []types.MempoolTx + TAdds []types.MempoolTx } // TreasuryPage is the page handler for the "/treasury" path func (exp *explorerUI) TreasuryPage(w http.ResponseWriter, r *http.Request) { - ctx := context.WithValue(r.Context(), ctxAddress, exp.pageData.HomeInfo.DevAddress) - r = r.WithContext(ctx) - if queryVals := r.URL.Query(); queryVals.Get("txntype") == "" { - queryVals.Set("txntype", "tspend") - r.URL.RawQuery = queryVals.Encode() - } - - limitN := defaultAddressRows - if nParam := r.URL.Query().Get("n"); nParam != "" { - val, err := strconv.ParseUint(nParam, 10, 64) - if err != nil { - exp.StatusPage(w, defaultErrorCode, "invalid n value", "", ExpStatusError) - return - } - if int64(val) > MaxTreasuryRows { - log.Warnf("TreasuryPage: requested up to %d address rows, "+ - "limiting to %d", limitN, MaxTreasuryRows) - limitN = MaxTreasuryRows - } else { - limitN = int64(val) - } - } - - // Number of txns to skip (OFFSET in database query). For UX reasons, the - // "start" URL query parameter is used. - var offset int64 - if startParam := r.URL.Query().Get("start"); startParam != "" { - val, err := strconv.ParseUint(startParam, 10, 64) - if err != nil { - exp.StatusPage(w, defaultErrorCode, "invalid start value", "", ExpStatusError) - return - } - offset = int64(val) + // Grab the URL query parameters + txType, txTypeStr, limitN, offset, err := parseTreasuryParams(r) + if err != nil { + log.Errorf("TreasuryPage request error: %v", err) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return } - // Transaction types to show. - txTypeStr := r.URL.Query().Get("txntype") - txType := parseTreasuryTransactionType(txTypeStr) - txns, err := exp.dataSource.TreasuryTxns(limitN, offset, txType) if exp.timeoutErrorPage(w, err, "TreasuryTxns") { return @@ -1458,6 +1433,7 @@ func (exp *explorerUI) TreasuryPage(w http.ResponseWriter, r *http.Request) { exp.pageData.RUnlock() typeCount := treasuryTypeCount(treasuryBalance, txType) + inv := exp.MempoolInventory() treasuryData := &TreasuryInfo{ Net: exp.ChainParams.Net.String(), @@ -1470,6 +1446,12 @@ func (exp *explorerUI) TreasuryPage(w http.ResponseWriter, r *http.Request) { Transactions: txns, Balance: treasuryBalance, TypeCount: typeCount, + Mempool: &TreasuryMempoolInfo{ + NumTSpends: inv.NumTSpends, + NumTAdds: inv.NumTAdds, + TSpends: inv.TSpends, + TAdds: inv.TAdds, + }, } xcBot := exp.xcBot @@ -1478,19 +1460,17 @@ func (exp *explorerUI) TreasuryPage(w http.ResponseWriter, r *http.Request) { } // Execute the HTML template. - linkTemplate := fmt.Sprintf("/treasury?start=%%d&n=%d&txntype=%v", limitN, txType) + linkTemplate := fmt.Sprintf("/treasury?start=%%d&n=%d&txntype=%s", limitN, txTypeStr) pageData := struct { *CommonPageData Data *TreasuryInfo FiatBalance *exchanges.Conversion Pages []pageNumber - Mempool *types.MempoolInfo }{ CommonPageData: exp.commonData(r), Data: treasuryData, FiatBalance: exp.xcBot.Conversion(dcrutil.Amount(treasuryBalance.Balance).ToCoin()), Pages: calcPages(int(typeCount), int(limitN), int(offset), linkTemplate), - Mempool: exp.MempoolInventory(), } str, err := exp.templates.exec("treasury", pageData) if err != nil { @@ -1679,7 +1659,7 @@ func (exp *explorerUI) AddressTable(w http.ResponseWriter, r *http.Request) { // TreasuryTable is the handler for the "/treasurytable" path. func (exp *explorerUI) TreasuryTable(w http.ResponseWriter, r *http.Request) { // Grab the URL query parameters - txType, limitN, offset, err := parseTreasuryParams(r) + txType, txTypeStr, limitN, offset, err := parseTreasuryParams(r) if err != nil { log.Errorf("TreasuryTable request error: %v", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) @@ -1698,8 +1678,7 @@ func (exp *explorerUI) TreasuryTable(w http.ResponseWriter, r *http.Request) { bal := exp.pageData.HomeInfo.TreasuryBalance exp.pageData.RUnlock() - linkTemplate := "/treasury" + "?start=%d&n=" + strconv.FormatInt(limitN, 10) + "&txntype=" + fmt.Sprintf("%v", txType) - + linkTemplate := fmt.Sprintf("/treasury?start=%%d&n=%d&txntype=%s", limitN, txTypeStr) response := struct { TxnCount int64 `json:"tx_count"` HTML string `json:"html"` @@ -1751,7 +1730,7 @@ func parseAddressParams(r *http.Request) (address string, txnType dbtypes.AddrTx return } - tType, limitN, offsetAddrOuts, err := parsePaginationParams(r) + tType, limitN, offsetAddrOuts, err := parsePaginationParams(r, true) txnType = dbtypes.AddrTxnViewTypeFromStr(tType) if txnType == dbtypes.AddrTxnUnknown { err = fmt.Errorf("unknown txntype query value") @@ -1791,21 +1770,20 @@ func parseTreasuryTransactionType(txnTypeStr string) (txType stake.TxType) { // parseTreasuryParams parses the tx filters for the treasury page. Used by both // TreasuryPage and TreasuryTable. -func parseTreasuryParams(r *http.Request) (txType stake.TxType, limitN, offsetAddrOuts int64, err error) { - tType, limitN, offsetAddrOuts, err := parsePaginationParams(r) - txType = parseTreasuryTransactionType(tType) +func parseTreasuryParams(r *http.Request) (txType stake.TxType, txTypeStr string, limitN, offsetAddrOuts int64, err error) { + txTypeStr, limitN, offsetAddrOuts, err = parsePaginationParams(r, false) + txType = parseTreasuryTransactionType(txTypeStr) return } // parsePaginationParams parses the pagination parameters from the query. The // txnType string is returned as-is. The caller must decipher the string. -func parsePaginationParams(r *http.Request) (txnType string, limitN, offset int64, err error) { +func parsePaginationParams(r *http.Request, isAddressPage bool) (txnType string, limitN, offset int64, err error) { // Number of outputs for the address to query the database for. The URL // query parameter "n" is used to specify the limit (e.g. "?n=20"). limitN = defaultAddressRows if nParam := r.URL.Query().Get("n"); nParam != "" { - var val uint64 val, err = strconv.ParseUint(nParam, 10, 64) if err != nil { @@ -1834,9 +1812,12 @@ func parsePaginationParams(r *http.Request) (txnType string, limitN, offset int6 } // Transaction types to show. - txnType = r.URL.Query().Get("txntype") - if txnType == "" { - txnType = "all" + if txnType = r.URL.Query().Get("txntype"); txnType == "" { + if isAddressPage { + txnType = "all" // Default to all types for address page + } else { + txnType = "tspend" // Default to tspend for treasury page + } } return diff --git a/cmd/dcrdata/public/js/controllers/address_controller.js b/cmd/dcrdata/public/js/controllers/address_controller.js index 7ae6bb5f9..8e5b5bcd7 100644 --- a/cmd/dcrdata/public/js/controllers/address_controller.js +++ b/cmd/dcrdata/public/js/controllers/address_controller.js @@ -306,17 +306,18 @@ export default class extends Controller { e.preventDefault() const url = e.target.href const parser = new URL(url) - const start = parser.searchParams.get('start') - const pagesize = parser.searchParams.get('n') + const start = parseInt(parser.searchParams.get('start')) + const pagesize = parseInt(parser.searchParams.get('n')) const txntype = parser.searchParams.get('txntype') this.fetchTable(txntype, pagesize, start) } toPage (direction) { const params = ctrl.paginationParams - const count = ctrl.pageSize + const count = parseInt(ctrl.pageSize) + const offset = parseInt(params.offset) const txType = ctrl.txnType - let requestedOffset = params.offset + count * direction + let requestedOffset = offset + count * direction if (requestedOffset >= params.count) return if (requestedOffset < 0) requestedOffset = 0 ctrl.fetchTable(txType, count, requestedOffset) @@ -351,8 +352,9 @@ export default class extends Controller { setPageability () { const params = ctrl.paginationParams - const rowMax = params.count - const count = ctrl.pageSize + const rowMax = parseInt(params.count) + const count = parseInt(ctrl.pageSize) + const offset = parseInt(params.offset) if (ctrl.paginationParams.count === 0) { ctrl.paginationheaderTarget.classList.add('d-hide') } else { @@ -370,8 +372,8 @@ export default class extends Controller { el.classList.add('disabled') } } - setAbility(ctrl.pageplusTarget, params.offset + count < rowMax) - setAbility(ctrl.pageminusTarget, params.offset - count >= 0) + setAbility(ctrl.pageplusTarget, offset + count < rowMax) + setAbility(ctrl.pageminusTarget, offset - count >= 0) ctrl.pageSizeOptions.forEach((option) => { if (option.value > 100) { if (rowMax > 100) { @@ -387,9 +389,9 @@ export default class extends Controller { }) setAbility(ctrl.pagesizeTarget, rowMax > 20) const suffix = rowMax > 1 ? 's' : '' - let rangeEnd = params.offset + count + let rangeEnd = offset + count if (rangeEnd > rowMax) rangeEnd = rowMax - ctrl.rangeTarget.innerHTML = 'showing ' + (params.offset + 1) + ' – ' + + ctrl.rangeTarget.innerHTML = 'showing ' + (offset + 1) + ' – ' + rangeEnd + ' of ' + rowMax.toLocaleString() + ' transaction' + suffix } diff --git a/cmd/dcrdata/views/extras.tmpl b/cmd/dcrdata/views/extras.tmpl index c04f83593..087ce1fbd 100644 --- a/cmd/dcrdata/views/extras.tmpl +++ b/cmd/dcrdata/views/extras.tmpl @@ -193,7 +193,7 @@ data-turbolinks-suppress-warning > diff --git a/cmd/dcrdata/views/treasury.tmpl b/cmd/dcrdata/views/treasury.tmpl index 6c5ad60e0..d0f77462b 100644 --- a/cmd/dcrdata/views/treasury.tmpl +++ b/cmd/dcrdata/views/treasury.tmpl @@ -3,7 +3,6 @@ {{template "html-head" headData .CommonPageData "Decred Decentralized Treasury"}} {{template "navbar" . }} - {{- $mempool := .Mempool -}} {{- with .Data}} {{- $bal := .Balance -}} {{- $TxnCount := $bal.TxCount}} @@ -158,8 +157,8 @@
- {{if gt $mempool.NumTSpends 0 -}} - {{- range $mempool.TSpends -}} + {{if gt .Mempool.NumTSpends 0 -}} + {{- range .Mempool.TSpends -}}