From 04ea6dc85132ea79f16b9169cbdd82e918e54d45 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Sun, 8 Jun 2025 12:09:54 +0100 Subject: [PATCH 1/5] multi: set linkTemplate txntype correctly and refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes a bug where the int value of treasury tx types was used to construct page links instead of its string value. As a result, page navigation on the treasury page did not function correctly because the string value is expected to return correct table values. Others: - Replace duplicated code used in parsing URL params in *explorerUI.TreasuryPage with improved parseTreasuryParams helper function. - Remove unused code from when *explorerUI.AddressPage was used to display treasury data. - Remove resolved TODO and refactor how treasury mempool data is passed to the HTML template.
 Signed-off-by: Philemon Ukane --- .../internal/explorer/explorerroutes.go | 89 ++++++++----------- cmd/dcrdata/views/treasury.tmpl | 9 +- 2 files changed, 40 insertions(+), 58 deletions(-) diff --git a/cmd/dcrdata/internal/explorer/explorerroutes.go b/cmd/dcrdata/internal/explorer/explorerroutes.go index ef6498d0a..e96251a94 100644 --- a/cmd/dcrdata/internal/explorer/explorerroutes.go +++ b/cmd/dcrdata/internal/explorer/explorerroutes.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2024, The Decred developers +// Copyright (c) 2018-2025, The Decred developers // Copyright (c) 2017, The dcrdata developers // See LICENSE for details. @@ -1408,12 +1408,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 @@ -1421,49 +1415,33 @@ 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) - } - } + ctx := r.Context() - // 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(ctx, limitN, offset, txType) if exp.timeoutErrorPage(w, err, "TreasuryTxns") { return @@ -1477,6 +1455,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(), @@ -1489,6 +1468,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 @@ -1497,7 +1482,7 @@ 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 @@ -1704,7 +1689,7 @@ func (exp *explorerUI) TreasuryTable(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // 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) @@ -1723,7 +1708,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 := "/treasury" + "?start=%d&n=" + strconv.FormatInt(limitN, 10) + "&txntype=" + fmt.Sprintf("%s", txTypeStr) response := struct { TxnCount int64 `json:"tx_count"` @@ -1816,9 +1801,9 @@ 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) + txType = parseTreasuryTransactionType(txTypeStr) return } @@ -1830,7 +1815,6 @@ func parsePaginationParams(r *http.Request) (txnType string, limitN, offset int6 limitN = defaultAddressRows if nParam := r.URL.Query().Get("n"); nParam != "" { - var val uint64 val, err = strconv.ParseUint(nParam, 10, 64) if err != nil { @@ -1859,9 +1843,8 @@ 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 == "" { + txnType = "tspend" // Default to tspend } return diff --git a/cmd/dcrdata/views/treasury.tmpl b/cmd/dcrdata/views/treasury.tmpl index 8546b1af6..4f47f2248 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 -}} {{.Hash}} @@ -180,7 +179,7 @@ - {{if gt $mempool.NumTAdds 0 -}}{{- /* this will be rare, so only show the section header and table if needed */ -}} + {{if gt .Mempool.NumTAdds 0 -}}{{- /* this will be rare, so only show the section header and table if needed */ -}}
Unconfirmed Treasury Adds
@@ -193,7 +192,7 @@ - {{range $mempool.TAdds -}} + {{range .Mempool.TAdds -}} {{.Hash}} From 10b183d175ff208ada28421bc6980d74c62fc2d7 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Sun, 8 Jun 2025 12:36:42 +0100 Subject: [PATCH 2/5] cleanup Signed-off-by: Philemon Ukane --- cmd/dcrdata/internal/explorer/explorerroutes.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/dcrdata/internal/explorer/explorerroutes.go b/cmd/dcrdata/internal/explorer/explorerroutes.go index e96251a94..a155b0e1e 100644 --- a/cmd/dcrdata/internal/explorer/explorerroutes.go +++ b/cmd/dcrdata/internal/explorer/explorerroutes.go @@ -1488,13 +1488,11 @@ func (exp *explorerUI) TreasuryPage(w http.ResponseWriter, r *http.Request) { 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 { @@ -1708,8 +1706,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("%s", txTypeStr) - + linkTemplate := fmt.Sprintf("/treasury?start=%%d&n=%d&txntype=%s", limitN, txTypeStr) response := struct { TxnCount int64 `json:"tx_count"` HTML string `json:"html"` From db4ba8e98184bb83266c72a13972500fd73a0198 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Sun, 8 Jun 2025 13:48:41 +0100 Subject: [PATCH 3/5] chore: handle edge case in shared parsePaginationParams function parsePaginationParams is used for both address and treasury page. Both pages have different values as defaults. This commit ensures the correct default is used for each caller. Signed-off-by: Philemon Ukane --- cmd/dcrdata/internal/explorer/explorerroutes.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/dcrdata/internal/explorer/explorerroutes.go b/cmd/dcrdata/internal/explorer/explorerroutes.go index a155b0e1e..2d3369f99 100644 --- a/cmd/dcrdata/internal/explorer/explorerroutes.go +++ b/cmd/dcrdata/internal/explorer/explorerroutes.go @@ -1758,7 +1758,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") @@ -1799,14 +1799,14 @@ 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, txTypeStr string, limitN, offsetAddrOuts int64, err error) { - txTypeStr, limitN, offsetAddrOuts, err = parsePaginationParams(r) + 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 @@ -1841,7 +1841,11 @@ func parsePaginationParams(r *http.Request) (txnType string, limitN, offset int6 // Transaction types to show. if txnType = r.URL.Query().Get("txntype"); txnType == "" { - txnType = "tspend" // Default to tspend + if isAddressPage { + txnType = "all" // Default to all types for address page + } else { + txnType = "tspend" // Default to tspend for treasury page + } } return From a86ae1c6e016e2a104e4a85b20139bca2e833f7b Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Sun, 8 Jun 2025 13:53:58 +0100 Subject: [PATCH 4/5] fix pagination display on treasury and address pages Signed-off-by: Philemon Ukane --- .../js/controllers/address_controller.js | 22 ++++++++++--------- cmd/dcrdata/views/extras.tmpl | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) 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 1e7395bbb..acc4cd3d4 100644 --- a/cmd/dcrdata/views/extras.tmpl +++ b/cmd/dcrdata/views/extras.tmpl @@ -193,7 +193,7 @@ data-turbolinks-suppress-warning > From fcdd6e404f5fc6936dd44a0a6674d4b566a917c6 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Fri, 27 Feb 2026 14:45:46 +0100 Subject: [PATCH 5/5] npm run build Signed-off-by: Philemon Ukane --- cmd/dcrdata/views/extras.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dcrdata/views/extras.tmpl b/cmd/dcrdata/views/extras.tmpl index acc4cd3d4..5569a4e76 100644 --- a/cmd/dcrdata/views/extras.tmpl +++ b/cmd/dcrdata/views/extras.tmpl @@ -193,7 +193,7 @@ data-turbolinks-suppress-warning >