Skip to content

Commit 7b9420d

Browse files
committed
Add catchup indicator
1 parent 08dc7e7 commit 7b9420d

File tree

11 files changed

+1331
-30
lines changed

11 files changed

+1331
-30
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package httpimpl
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"time"
7+
8+
"github.com/bsv-blockchain/teranode/services/blockvalidation/blockvalidation_api"
9+
"github.com/labstack/echo/v4"
10+
"google.golang.org/grpc"
11+
"google.golang.org/grpc/credentials/insecure"
12+
)
13+
14+
// GetCatchupStatus returns the current catchup status from the BlockValidation service
15+
func (h *HTTP) GetCatchupStatus(c echo.Context) error {
16+
ctx, cancel := context.WithTimeout(c.Request().Context(), 5*time.Second)
17+
defer cancel()
18+
19+
// Connect to BlockValidation gRPC service
20+
blockvalidationAddr := h.settings.BlockValidation.GRPCListenAddress
21+
if blockvalidationAddr == "" {
22+
blockvalidationAddr = "localhost:8082" // default
23+
}
24+
25+
conn, err := grpc.DialContext(ctx, blockvalidationAddr,
26+
grpc.WithTransportCredentials(insecure.NewCredentials()),
27+
grpc.WithBlock(),
28+
)
29+
if err != nil {
30+
h.logger.Errorf("[GetCatchupStatus] Failed to connect to BlockValidation service: %v", err)
31+
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{
32+
"error": "Failed to connect to BlockValidation service",
33+
"is_catching_up": false,
34+
})
35+
}
36+
defer conn.Close()
37+
38+
// Call the GetCatchupStatus gRPC method
39+
client := blockvalidation_api.NewBlockValidationAPIClient(conn)
40+
resp, err := client.GetCatchupStatus(ctx, &blockvalidation_api.EmptyMessage{})
41+
if err != nil {
42+
h.logger.Errorf("[GetCatchupStatus] Failed to get catchup status: %v", err)
43+
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
44+
"error": "Failed to get catchup status",
45+
"is_catching_up": false,
46+
})
47+
}
48+
49+
// Convert gRPC response to JSON
50+
jsonResp := map[string]interface{}{
51+
"is_catching_up": resp.IsCatchingUp,
52+
"peer_id": resp.PeerId,
53+
"peer_url": resp.PeerUrl,
54+
"target_block_hash": resp.TargetBlockHash,
55+
"target_block_height": resp.TargetBlockHeight,
56+
"current_height": resp.CurrentHeight,
57+
"total_blocks": resp.TotalBlocks,
58+
"blocks_fetched": resp.BlocksFetched,
59+
"blocks_validated": resp.BlocksValidated,
60+
"start_time": resp.StartTime,
61+
"duration_ms": resp.DurationMs,
62+
"fork_depth": resp.ForkDepth,
63+
"common_ancestor_hash": resp.CommonAncestorHash,
64+
"common_ancestor_height": resp.CommonAncestorHeight,
65+
}
66+
67+
// Add previous attempt if available
68+
if resp.PreviousAttempt != nil {
69+
jsonResp["previous_attempt"] = map[string]interface{}{
70+
"peer_id": resp.PreviousAttempt.PeerId,
71+
"peer_url": resp.PreviousAttempt.PeerUrl,
72+
"target_block_hash": resp.PreviousAttempt.TargetBlockHash,
73+
"target_block_height": resp.PreviousAttempt.TargetBlockHeight,
74+
"error_message": resp.PreviousAttempt.ErrorMessage,
75+
"error_type": resp.PreviousAttempt.ErrorType,
76+
"attempt_time": resp.PreviousAttempt.AttemptTime,
77+
"duration_ms": resp.PreviousAttempt.DurationMs,
78+
"blocks_validated": resp.PreviousAttempt.BlocksValidated,
79+
}
80+
}
81+
82+
return c.JSON(http.StatusOK, jsonResp)
83+
}

services/asset/httpimpl/http.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ func New(logger ulogger.Logger, tSettings *settings.Settings, repo *repository.R
327327
apiGroup.POST("/block/revalidate", blockHandler.RevalidateBlock)
328328
apiGroup.GET("/blocks/invalid", blockHandler.GetLastNInvalidBlocks)
329329

330+
// Register catchup status endpoint
331+
apiGroup.GET("/catchup/status", h.GetCatchupStatus)
332+
330333
// Add OPTIONS handlers for block operations
331334
apiGroup.OPTIONS("/block/invalidate", func(c echo.Context) error {
332335
return c.NoContent(http.StatusOK)

services/blockvalidation/Server.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,24 @@ type Server struct {
213213
// The success rate can be calculated as: catchupSuccesses / catchupAttempts.
214214
// The value persists for the lifetime of the server and is never reset.
215215
catchupSuccesses atomic.Int64
216+
217+
// activeCatchupCtx stores the current catchup context for status reporting to the dashboard.
218+
// This is updated when a catchup operation starts and cleared when it completes.
219+
// Protected by activeCatchupCtxMu for thread-safe access.
220+
activeCatchupCtx *CatchupContext
221+
activeCatchupCtxMu sync.RWMutex
222+
223+
// catchupProgress tracks the current progress through block headers during catchup.
224+
// blocksFetched and blocksValidated are updated as blocks are processed.
225+
// These counters are reset at the start of each catchup operation.
226+
// Protected by activeCatchupCtxMu for thread-safe access.
227+
blocksFetched atomic.Int64
228+
blocksValidated atomic.Int64
229+
230+
// previousCatchupAttempt stores details about the last failed catchup attempt.
231+
// This is used to display in the dashboard why we switched from one peer to another.
232+
// Protected by activeCatchupCtxMu for thread-safe access.
233+
previousCatchupAttempt *PreviousAttempt
216234
}
217235

218236
// New creates a new block validation server with the provided dependencies.
@@ -427,6 +445,61 @@ func (u *Server) HealthGRPC(ctx context.Context, _ *blockvalidation_api.EmptyMes
427445
}, errors.WrapGRPC(err)
428446
}
429447

448+
// GetCatchupStatus returns the current catchup status via gRPC.
449+
// This method provides real-time information about ongoing catchup operations
450+
// for monitoring and dashboard display purposes.
451+
//
452+
// The response includes details about the peer being synced from, progress metrics,
453+
// and timing information. If no catchup is active, the response will indicate
454+
// IsCatchingUp=false and other fields will be empty/zero.
455+
//
456+
// This method is thread-safe and can be called concurrently with catchup operations.
457+
//
458+
// Parameters:
459+
// - ctx: Context for the gRPC request
460+
// - _: Empty request message (unused but required by gRPC interface)
461+
//
462+
// Returns:
463+
// - *blockvalidation_api.CatchupStatusResponse: Current catchup status
464+
// - error: Any error encountered (always nil for this method)
465+
func (u *Server) GetCatchupStatus(ctx context.Context, _ *blockvalidation_api.EmptyMessage) (*blockvalidation_api.CatchupStatusResponse, error) {
466+
status := u.getCatchupStatusInternal()
467+
468+
resp := &blockvalidation_api.CatchupStatusResponse{
469+
IsCatchingUp: status.IsCatchingUp,
470+
PeerId: status.PeerID,
471+
PeerUrl: status.PeerURL,
472+
TargetBlockHash: status.TargetBlockHash,
473+
TargetBlockHeight: status.TargetBlockHeight,
474+
CurrentHeight: status.CurrentHeight,
475+
TotalBlocks: int32(status.TotalBlocks),
476+
BlocksFetched: status.BlocksFetched,
477+
BlocksValidated: status.BlocksValidated,
478+
StartTime: status.StartTime,
479+
DurationMs: status.DurationMs,
480+
ForkDepth: status.ForkDepth,
481+
CommonAncestorHash: status.CommonAncestorHash,
482+
CommonAncestorHeight: status.CommonAncestorHeight,
483+
}
484+
485+
// Add previous attempt if available
486+
if status.PreviousAttempt != nil {
487+
resp.PreviousAttempt = &blockvalidation_api.PreviousCatchupAttempt{
488+
PeerId: status.PreviousAttempt.PeerID,
489+
PeerUrl: status.PreviousAttempt.PeerURL,
490+
TargetBlockHash: status.PreviousAttempt.TargetBlockHash,
491+
TargetBlockHeight: status.PreviousAttempt.TargetBlockHeight,
492+
ErrorMessage: status.PreviousAttempt.ErrorMessage,
493+
ErrorType: status.PreviousAttempt.ErrorType,
494+
AttemptTime: status.PreviousAttempt.AttemptTime,
495+
DurationMs: status.PreviousAttempt.DurationMs,
496+
BlocksValidated: status.PreviousAttempt.BlocksValidated,
497+
}
498+
}
499+
500+
return resp, nil
501+
}
502+
430503
// Init initializes the block validation server with required dependencies and services.
431504
// It establishes connections to subtree validation services, configures UTXO store access,
432505
// and starts background processing components. This method must be called before Start().

0 commit comments

Comments
 (0)