diff --git a/cmd/dcrdata/internal/api/apiroutes.go b/cmd/dcrdata/internal/api/apiroutes.go
index 5dd518871..5cfe98875 100644
--- a/cmd/dcrdata/internal/api/apiroutes.go
+++ b/cmd/dcrdata/internal/api/apiroutes.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2022, The Decred developers
+// Copyright (c) 2018-2025, The Decred developers
// Copyright (c) 2017, The dcrdata developers
// See LICENSE for details.
@@ -46,6 +46,11 @@ import (
// once.
const maxBlockRangeCount = 1000
+// noConnectionError is the error message returned by updateNodeConnections when
+// there are no connection to a dcrd node. This error means dcrdata has to be
+// restarted because auto reconnect has been disabled at the time of writing.
+var noConnectionError = errors.New("failed to get connection count")
+
// DataSource specifies an interface for advanced data collection using the
// auxiliary DB (e.g. PostgreSQL).
type DataSource interface {
@@ -169,9 +174,10 @@ func NewContext(cfg *AppContextConfig) *appContext {
func (c *appContext) updateNodeConnections() error {
nodeConnections, err := c.nodeClient.GetConnectionCount(context.TODO())
if err != nil {
- // Assume there arr no connections if RPC had an error.
+ // Assume there are no connections if RPC had an error.
+ c.Status.SetReady(false)
c.Status.SetConnections(0)
- return fmt.Errorf("failed to get connection count: %v", err)
+ return fmt.Errorf("%w: %v", noConnectionError, err)
}
// Before updating connections, get the previous connection count.
@@ -223,7 +229,14 @@ out:
case <-rpcCheckTicker.C:
if err := c.updateNodeConnections(); err != nil {
log.Warn("updateNodeConnections: ", err)
- break keepon
+
+ if !errors.Is(err, noConnectionError) {
+ break keepon
+ }
+
+ log.Warn("Exiting block connected handler for STATUS monitor.")
+ rpcCheckTicker.Stop()
+ break out
}
case height, ok := <-wireHeightChan:
diff --git a/cmd/dcrdata/internal/explorer/explorer.go b/cmd/dcrdata/internal/explorer/explorer.go
index 660338544..91e98f7aa 100644
--- a/cmd/dcrdata/internal/explorer/explorer.go
+++ b/cmd/dcrdata/internal/explorer/explorer.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.
@@ -28,6 +28,7 @@ import (
"github.com/decred/dcrdata/exchanges/v3"
"github.com/decred/dcrdata/gov/v6/agendas"
pitypes "github.com/decred/dcrdata/gov/v6/politeia/types"
+ apitypes "github.com/decred/dcrdata/v8/api/types"
"github.com/decred/dcrdata/v8/blockdata"
"github.com/decred/dcrdata/v8/db/dbtypes"
"github.com/decred/dcrdata/v8/explorer/types"
@@ -228,6 +229,7 @@ type explorerUI struct {
invsMtx sync.RWMutex
invs *types.MempoolInfo
premine int64
+ status *apitypes.Status
}
// AreDBsSyncing is a thread-safe way to fetch the boolean in dbsSyncing.
@@ -276,6 +278,12 @@ func (exp *explorerUI) StopWebsocketHub() {
close(exp.xcDone)
}
+// SetStatus updates exp.status and MUST not be used in a goroutine to avoid
+// data races.
+func (exp *explorerUI) SetStatus(status *apitypes.Status) {
+ exp.status = status
+}
+
// ExplorerConfig is the configuration settings for explorerUI.
type ExplorerConfig struct {
DataSource explorerDataSource
diff --git a/cmd/dcrdata/internal/explorer/explorermiddleware.go b/cmd/dcrdata/internal/explorer/explorermiddleware.go
index a5a071650..fff437a9f 100644
--- a/cmd/dcrdata/internal/explorer/explorermiddleware.go
+++ b/cmd/dcrdata/internal/explorer/explorermiddleware.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, The Decred developers
+// Copyright (c) 2018-2025, The Decred developers
// Copyright (c) 2017, The dcrdata developers
// See LICENSE for details.
@@ -146,39 +146,54 @@ func (exp *explorerUI) BlockHashPathOrIndexCtx(next http.Handler) http.Handler {
})
}
-// SyncStatusPageIntercept serves only the syncing status page until it is
-// deactivated when ShowingSyncStatusPage is set to false. This page is served
-// for all the possible routes supported until the background syncing is done.
-func (exp *explorerUI) SyncStatusPageIntercept(next http.Handler) http.Handler {
+// StatusPageIntercept serves the syncing status page when
+// exp.ShowingSyncStatusPage is set to true until when exp.ShowingSyncStatusPage
+// is set to false. This page is served for all the possible routes supported
+// until the background syncing is done. If exp.Ready is false, an error
+// StatusPage is served.
+func (exp *explorerUI) StatusPageIntercept(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if exp.ShowingSyncStatusPage() {
exp.StatusPage(w, "Database Update Running. Please Wait.",
"Blockchain sync is running. Please wait.", "", ExpStatusSyncing)
return
}
+
+ if !exp.status.Ready() {
+ exp.StatusPage(w, defaultErrorCode, "Uh Oh. Something unexpected happened, try again later. If you see this error message, please reach out to us via matrix or other communication channel.", "", ExpStatusError)
+ return
+ }
+
// Otherwise, proceed to the next http handler.
next.ServeHTTP(w, r)
})
}
-// SyncStatusAPIIntercept returns a json response back instead of a web page
-// when display sync status is active for the api endpoints supported.
-func (exp *explorerUI) SyncStatusAPIIntercept(next http.Handler) http.Handler {
+// APIStatusIntercept returns a json response back instead of a web page for the
+// api endpoints supported when display sync status is active or explore is not
+// ready.
+func (exp *explorerUI) APIStatusIntercept(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if exp.ShowingSyncStatusPage() {
exp.HandleApiRequestsOnSync(w, r)
return
}
+
+ if !exp.status.Ready() {
+ exp.HandleAPiRequestWhenNotReady(w)
+ return
+ }
+
// Otherwise, proceed to the next http handler.
next.ServeHTTP(w, r)
})
}
-// SyncStatusFileIntercept triggers an HTTP error if a file is requested for
-// download before the DB is synced.
-func (exp *explorerUI) SyncStatusFileIntercept(next http.Handler) http.Handler {
+// ExplorerStatusFileIntercept triggers an HTTP error if a file is requested for
+// download before the DB is synced or when explorer is not ready.
+func (exp *explorerUI) ExplorerStatusFileIntercept(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if exp.ShowingSyncStatusPage() {
+ if exp.ShowingSyncStatusPage() || !exp.status.Ready() {
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
diff --git a/cmd/dcrdata/internal/explorer/explorerroutes.go b/cmd/dcrdata/internal/explorer/explorerroutes.go
index 323601806..ce8f194d0 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.
@@ -73,6 +73,7 @@ type CommonPageData struct {
BaseURL string // scheme + "://" + "host"
Path string
RequestURI string // path?query
+ Ready bool
}
// FullURL constructs the page's complete URL.
@@ -2458,6 +2459,14 @@ func (exp *explorerUI) HandleApiRequestsOnSync(w http.ResponseWriter, r *http.Re
io.WriteString(w, str)
}
+// HandleAPiRequestWhenNotReady handles all API request when the explorer is not
+// ready.
+func (exp *explorerUI) HandleAPiRequestWhenNotReady(w http.ResponseWriter) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusServiceUnavailable)
+ io.WriteString(w, `{"message": "Something went wrong, explorer is not ready. Please try again later."}`)
+}
+
// MarketPage is the page handler for the "/market" path.
func (exp *explorerUI) MarketPage(w http.ResponseWriter, r *http.Request) {
str, err := exp.templates.exec("market", struct {
@@ -2519,6 +2528,7 @@ func (exp *explorerUI) commonData(r *http.Request) *CommonPageData {
BaseURL: baseURL,
Path: r.URL.Path,
RequestURI: r.URL.RequestURI(),
+ Ready: exp.status.Ready() || exp.ShowingSyncStatusPage(),
}
}
diff --git a/cmd/dcrdata/internal/explorer/explorerroutes_test.go b/cmd/dcrdata/internal/explorer/explorerroutes_test.go
index 2f1c36615..d3ce6f2e1 100644
--- a/cmd/dcrdata/internal/explorer/explorerroutes_test.go
+++ b/cmd/dcrdata/internal/explorer/explorerroutes_test.go
@@ -7,6 +7,7 @@ import (
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrdata/db/dcrpg/v8"
+ apitypes "github.com/decred/dcrdata/v8/api/types"
"github.com/decred/dcrdata/v8/explorer/types"
)
@@ -71,6 +72,8 @@ func TestStatusPageResponseCodes(t *testing.T) {
TestnetLink: "/",
})
+ exp.SetStatus(new(apitypes.Status))
+
// handler := http.HandlerFunc()
// handler.ServeHTTP(rr, req)
diff --git a/cmd/dcrdata/internal/explorer/templates.go b/cmd/dcrdata/internal/explorer/templates.go
index 79800b95f..172488f1e 100644
--- a/cmd/dcrdata/internal/explorer/templates.go
+++ b/cmd/dcrdata/internal/explorer/templates.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.
@@ -78,14 +78,14 @@ func (t *templates) reloadTemplates() error {
if errorStrings == nil {
return nil
}
- return fmt.Errorf(strings.Join(errorStrings, " | "))
+ return fmt.Errorf("%s", strings.Join(errorStrings, " | "))
}
// execTemplateToString executes the associated input template using the
// supplied data, and writes the result into a string. If the template fails to
// execute or isn't found, a non-nil error will be returned. Check it before
-// writing to theclient, otherwise you might as well execute directly into
-// your response writer instead of the internal buffer of this function.
+// writing to the client, otherwise you might as well execute directly into your
+// response writer instead of the internal buffer of this function.
func (t *templates) execTemplateToString(name string, data interface{}) (string, error) {
temp, ok := t.templates[name]
if !ok {
diff --git a/cmd/dcrdata/main.go b/cmd/dcrdata/main.go
index ca925862d..58cb6f763 100644
--- a/cmd/dcrdata/main.go
+++ b/cmd/dcrdata/main.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2024, The Decred developers
+// Copyright (c) 2018-2025, The Decred developers
// Copyright (c) 2017, Jonathan Chappelow
// See LICENSE for details.
@@ -651,6 +651,10 @@ func _main(ctx context.Context) error {
chainDB.SignalHeight(uint32(chainDBHeight))
}
+ // Set explore status. This will enable tracking whether or not we are still
+ // connected to a node.
+ explore.SetStatus(app.Status)
+
// Configure the URL path to http handler router for the API.
apiMux := api.NewAPIRouter(app, cfg.IndentJSON, cfg.UseRealIP, cfg.CompressAPI)
@@ -688,7 +692,7 @@ func _main(ctx context.Context) error {
webMux.Use(explorer.AllowedHosts(cfg.AllowedHosts))
}
- webMux.With(explore.SyncStatusPageIntercept).Group(func(r chi.Router) {
+ webMux.With(explore.StatusPageIntercept).Group(func(r chi.Router) {
r.Get("/", explore.Home)
r.Get("/visualblocks", explore.VisualBlocks)
})
@@ -724,9 +728,8 @@ func _main(ctx context.Context) error {
webMux.Mount(profPath, http.StripPrefix(profPath, http.DefaultServeMux))
}
- // SyncStatusAPIIntercept returns a json response if the sync status page is
- // enabled (no the full explorer while syncing).
- webMux.With(explore.SyncStatusAPIIntercept).Group(func(r chi.Router) {
+ // APIStatusIntercept returns a json response if the status page if enabled.
+ webMux.With(explore.APIStatusIntercept).Group(func(r chi.Router) {
// Mount the dcrdata's REST API.
r.Mount("/api", apiMux.Mux)
// Setup and mount the Insight API.
@@ -743,11 +746,11 @@ func _main(ctx context.Context) error {
})
// HTTP Error 503 StatusServiceUnavailable for file requests before sync.
- webMux.With(explore.SyncStatusFileIntercept).Group(func(r chi.Router) {
+ webMux.With(explore.ExplorerStatusFileIntercept).Group(func(r chi.Router) {
r.Mount("/download", fileMux.Mux)
})
- webMux.With(explore.SyncStatusPageIntercept).Group(func(r chi.Router) {
+ webMux.With(explore.StatusPageIntercept).Group(func(r chi.Router) {
r.NotFound(explore.NotFound)
r.Mount("/explorer", explore.Mux) // legacy
diff --git a/cmd/dcrdata/views/extras.tmpl b/cmd/dcrdata/views/extras.tmpl
index 1330ffbc5..c0f2c0abe 100644
--- a/cmd/dcrdata/views/extras.tmpl
+++ b/cmd/dcrdata/views/extras.tmpl
@@ -176,6 +176,7 @@
rel="noopener noreferrer"
>© {{currentYear}} The Decred developers (ISC)
+ {{if .Ready }}
Connecting to WebSocket...
+ {{else}}
+
+ Not ready...
+
+ {{end}}