Skip to content

Commit 2cf0e3b

Browse files
authored
integrate ctx and p2p updates from old repo (#12)
* integrate ctx and p2p updates from old repo * lint fix * clean up context * get header by height * fix bootstrap * fix context cancellation * fix deps * fix context in sse stream * update go-sdk * Fix tests * lint fix
1 parent 85d256b commit 2cf0e3b

21 files changed

+345
-223
lines changed

cmd/server/api.go

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,19 @@ import (
2323
var openapiSpec string
2424

2525
// Server wraps the ChainManager with Fiber handlers
26+
//
27+
//nolint:containedctx // Context stored for SSE stream shutdown detection
2628
type Server struct {
29+
ctx context.Context
2730
cm *chaintracks.ChainManager
2831
sseClients map[int64]*bufio.Writer
2932
sseClientsMu sync.RWMutex
3033
}
3134

3235
// NewServer creates a new API server
33-
func NewServer(cm *chaintracks.ChainManager) *Server {
36+
func NewServer(ctx context.Context, cm *chaintracks.ChainManager) *Server {
3437
return &Server{
38+
ctx: ctx,
3539
cm: cm,
3640
sseClients: make(map[int64]*bufio.Writer),
3741
}
@@ -97,6 +101,9 @@ func (s *Server) HandleTipStream(c *fiber.Ctx) error {
97101
c.Set("Connection", "keep-alive")
98102
c.Set("Transfer-Encoding", "chunked")
99103

104+
// Capture context before entering stream writer
105+
ctx := c.UserContext()
106+
100107
c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
101108
clientID := time.Now().UnixNano()
102109

@@ -111,7 +118,7 @@ func (s *Server) HandleTipStream(c *fiber.Ctx) error {
111118
}()
112119

113120
// Send initial tip
114-
tip := s.cm.GetTip()
121+
tip := s.cm.GetTip(ctx)
115122
if tip != nil { //nolint:nestif // SSE stream initialization logic
116123

117124
data, err := json.Marshal(tip)
@@ -129,15 +136,17 @@ func (s *Server) HandleTipStream(c *fiber.Ctx) error {
129136
ticker := time.NewTicker(15 * time.Second)
130137
defer ticker.Stop()
131138

132-
for range ticker.C {
133-
// Send keepalive comment
134-
if _, writeErr := fmt.Fprintf(w, ": keepalive\n\n"); writeErr != nil {
135-
// Connection closed
136-
return
137-
}
138-
if err := w.Flush(); err != nil {
139-
// Connection closed
139+
for {
140+
select {
141+
case <-s.ctx.Done():
140142
return
143+
case <-ticker.C:
144+
if _, writeErr := fmt.Fprintf(w, ": keepalive\n\n"); writeErr != nil {
145+
return
146+
}
147+
if err := w.Flush(); err != nil {
148+
return
149+
}
141150
}
142151
}
143152
}))
@@ -169,7 +178,7 @@ func (s *Server) HandleRobots(c *fiber.Ctx) error {
169178

170179
// HandleGetNetwork returns the network name
171180
func (s *Server) HandleGetNetwork(c *fiber.Ctx) error {
172-
network, err := s.cm.GetNetwork()
181+
network, err := s.cm.GetNetwork(c.UserContext())
173182
if err != nil {
174183
return c.Status(fiber.StatusInternalServerError).JSON(Response{
175184
Status: "error",
@@ -187,15 +196,15 @@ func (s *Server) HandleGetHeight(c *fiber.Ctx) error {
187196
c.Set("Cache-Control", "public, max-age=60")
188197
return c.JSON(Response{
189198
Status: "success",
190-
Value: s.cm.GetHeight(),
199+
Value: s.cm.GetHeight(c.UserContext()),
191200
})
192201
}
193202

194203
// HandleGetTipHash returns the chain tip hash
195204
func (s *Server) HandleGetTipHash(c *fiber.Ctx) error {
196205
c.Set("Cache-Control", "no-cache")
197206

198-
tip := s.cm.GetTip()
207+
tip := s.cm.GetTip(c.UserContext())
199208
if tip == nil {
200209
return c.Status(fiber.StatusNotFound).JSON(Response{
201210
Status: "error",
@@ -215,7 +224,7 @@ func (s *Server) HandleGetTipHash(c *fiber.Ctx) error {
215224
func (s *Server) HandleGetTipHeader(c *fiber.Ctx) error {
216225
c.Set("Cache-Control", "no-cache")
217226

218-
tip := s.cm.GetTip()
227+
tip := s.cm.GetTip(c.UserContext())
219228
if tip == nil {
220229
return c.Status(fiber.StatusNotFound).JSON(Response{
221230
Status: "error",
@@ -250,18 +259,19 @@ func (s *Server) HandleGetHeaderByHeight(c *fiber.Ctx) error {
250259
})
251260
}
252261

253-
tip := s.cm.GetHeight()
262+
tip := s.cm.GetHeight(c.UserContext())
254263
if uint32(height) < tip-100 {
255264
c.Set("Cache-Control", "public, max-age=3600")
256265
} else {
257266
c.Set("Cache-Control", "no-cache")
258267
}
259268

260-
header, err := s.cm.GetHeaderByHeight(uint32(height))
269+
header, err := s.cm.GetHeaderByHeight(c.UserContext(), uint32(height))
261270
if err != nil {
262-
return c.JSON(Response{
263-
Status: "success",
264-
Value: nil,
271+
return c.Status(fiber.StatusNotFound).JSON(Response{
272+
Status: "error",
273+
Code: "ERR_NOT_FOUND",
274+
Description: "Header not found at height " + heightStr,
265275
})
266276
}
267277

@@ -291,15 +301,16 @@ func (s *Server) HandleGetHeaderByHash(c *fiber.Ctx) error {
291301
})
292302
}
293303

294-
header, err := s.cm.GetHeaderByHash(hash)
304+
header, err := s.cm.GetHeaderByHash(c.UserContext(), hash)
295305
if err != nil {
296-
return c.JSON(Response{
297-
Status: "success",
298-
Value: nil,
306+
return c.Status(fiber.StatusNotFound).JSON(Response{
307+
Status: "error",
308+
Code: "ERR_NOT_FOUND",
309+
Description: "Header not found for hash " + hashStr,
299310
})
300311
}
301312

302-
tip := s.cm.GetHeight()
313+
tip := s.cm.GetHeight(c.UserContext())
303314
if header.Height < tip-100 {
304315
c.Set("Cache-Control", "public, max-age=3600")
305316
} else {
@@ -343,7 +354,7 @@ func (s *Server) HandleGetHeaders(c *fiber.Ctx) error {
343354
})
344355
}
345356

346-
tip := s.cm.GetHeight()
357+
tip := s.cm.GetHeight(c.UserContext())
347358
if uint32(height) < tip-100 {
348359
c.Set("Cache-Control", "public, max-age=3600")
349360
} else {
@@ -353,7 +364,7 @@ func (s *Server) HandleGetHeaders(c *fiber.Ctx) error {
353364
var hexData string
354365
for i := uint32(0); i < uint32(count); i++ {
355366
h := uint32(height) + i
356-
header, err := s.cm.GetHeaderByHeight(h)
367+
header, err := s.cm.GetHeaderByHeight(c.UserContext(), h)
357368
if err != nil {
358369
break
359370
}

cmd/server/api_test.go

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func TestHandleGetNetwork(t *testing.T) {
2323

2424
func TestHandleGetHeight(t *testing.T) {
2525
app, cm := setupTestApp(t)
26+
ctx := t.Context()
2627

2728
resp := httpGet(t, app, "/v2/height")
2829
requireStatus(t, resp, 200)
@@ -35,11 +36,12 @@ func TestHandleGetHeight(t *testing.T) {
3536
parseJSONResponse(t, resp.Body, &response)
3637

3738
assert.Equal(t, "success", response.Status)
38-
assert.Equal(t, cm.GetHeight(), uint32(response.Value))
39+
assert.Equal(t, cm.GetHeight(ctx), uint32(response.Value))
3940
}
4041

4142
func TestHandleGetTipHash(t *testing.T) {
4243
app, cm := setupTestApp(t)
44+
ctx := t.Context()
4345

4446
resp := httpGet(t, app, "/v2/tip/hash")
4547
requireStatus(t, resp, 200)
@@ -52,11 +54,12 @@ func TestHandleGetTipHash(t *testing.T) {
5254
parseJSONResponse(t, resp.Body, &response)
5355

5456
assert.Equal(t, "success", response.Status)
55-
assert.Equal(t, cm.GetTip().Header.Hash().String(), response.Value)
57+
assert.Equal(t, cm.GetTip(ctx).Header.Hash().String(), response.Value)
5658
}
5759

5860
func TestHandleGetTipHeader(t *testing.T) {
5961
app, cm := setupTestApp(t)
62+
ctx := t.Context()
6063

6164
resp := httpGet(t, app, "/v2/tip/header")
6265
requireStatus(t, resp, 200)
@@ -68,13 +71,14 @@ func TestHandleGetTipHeader(t *testing.T) {
6871
parseJSONResponse(t, resp.Body, &response)
6972

7073
assert.Equal(t, "success", response.Status)
71-
assert.Equal(t, cm.GetTip().Height, response.Value.Height)
74+
assert.Equal(t, cm.GetTip(ctx).Height, response.Value.Height)
7275
}
7376

7477
func TestHandleGetHeaderByHeight(t *testing.T) {
7578
app, cm := setupTestApp(t)
79+
ctx := t.Context()
7680

77-
if cm.GetHeight() < 100 {
81+
if cm.GetHeight(ctx) < 100 {
7882
t.Skip("Not enough headers to test")
7983
}
8084

@@ -95,22 +99,15 @@ func TestHandleGetHeaderByHeight_NotFound(t *testing.T) {
9599
app, _ := setupTestApp(t)
96100

97101
resp := httpGet(t, app, "/v2/header/height/99999999")
98-
requireStatus(t, resp, 200)
99-
100-
var response struct {
101-
Status string `json:"status"`
102-
Value interface{} `json:"value"`
103-
}
104-
parseJSONResponse(t, resp.Body, &response)
105-
106-
assert.Equal(t, "success", response.Status)
107-
assert.Nil(t, response.Value, "Expected null value for non-existent header")
102+
requireStatus(t, resp, 404)
103+
requireErrorResponse(t, resp.Body)
108104
}
109105

110106
func TestHandleGetHeaderByHash(t *testing.T) {
111107
app, cm := setupTestApp(t)
108+
ctx := t.Context()
112109

113-
tip := cm.GetTip()
110+
tip := cm.GetTip(ctx)
114111
hash := tip.Header.Hash().String()
115112

116113
resp := httpGet(t, app, "/v2/header/hash/"+hash)
@@ -139,22 +136,15 @@ func TestHandleGetHeaderByHash_NotFound(t *testing.T) {
139136

140137
nonExistentHash := chainhash.Hash{}
141138
resp := httpGet(t, app, "/v2/header/hash/"+nonExistentHash.String())
142-
requireStatus(t, resp, 200)
143-
144-
var response struct {
145-
Status string `json:"status"`
146-
Value interface{} `json:"value"`
147-
}
148-
parseJSONResponse(t, resp.Body, &response)
149-
150-
assert.Equal(t, "success", response.Status)
151-
assert.Nil(t, response.Value, "Expected null value for non-existent header")
139+
requireStatus(t, resp, 404)
140+
requireErrorResponse(t, resp.Body)
152141
}
153142

154143
func TestHandleGetHeaders(t *testing.T) {
155144
app, cm := setupTestApp(t)
145+
ctx := t.Context()
156146

157-
if cm.GetHeight() < 10 {
147+
if cm.GetHeight(ctx) < 10 {
158148
t.Skip("Not enough headers to test")
159149
}
160150

cmd/server/config.go

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package main
22

33
import (
4+
"encoding/json"
5+
"log"
46
"os"
57
"path/filepath"
68
"strconv"
79
)
810

911
// Config holds the server configuration
1012
type Config struct {
11-
Port int
12-
Network string
13-
StoragePath string
14-
BootstrapURL string
13+
Port int
14+
Network string
15+
StoragePath string
16+
BootstrapURL string
17+
BootstrapPeers []string
1518
}
1619

1720
// LoadConfig loads configuration from environment variables with defaults
@@ -35,12 +38,38 @@ func LoadConfig() *Config {
3538

3639
bootstrapURL := os.Getenv("BOOTSTRAP_URL")
3740

41+
bootstrapPeers := loadBootstrapPeers(network)
42+
3843
return &Config{
39-
Port: port,
40-
Network: network,
41-
StoragePath: storagePath,
42-
BootstrapURL: bootstrapURL,
44+
Port: port,
45+
Network: network,
46+
StoragePath: storagePath,
47+
BootstrapURL: bootstrapURL,
48+
BootstrapPeers: bootstrapPeers,
49+
}
50+
}
51+
52+
// loadBootstrapPeers loads P2P bootstrap peers from data/bootstrap_peers.json
53+
func loadBootstrapPeers(network string) []string {
54+
data, err := os.ReadFile("data/bootstrap_peers.json")
55+
if err != nil {
56+
log.Printf("Warning: could not load bootstrap_peers.json: %v", err)
57+
return nil
4358
}
59+
60+
var peers map[string][]string
61+
if err := json.Unmarshal(data, &peers); err != nil {
62+
log.Printf("Warning: could not parse bootstrap_peers.json: %v", err)
63+
return nil
64+
}
65+
66+
if networkPeers, ok := peers[network]; ok {
67+
log.Printf("Loaded %d bootstrap peers for network %s", len(networkPeers), network)
68+
return networkPeers
69+
}
70+
71+
log.Printf("Warning: no bootstrap peers found for network %s", network)
72+
return nil
4473
}
4574

4675
// getDefaultStoragePath returns ~/.chaintracks as the default storage path

cmd/server/dashboard.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ func NewDashboardHandler(server *Server) *DashboardHandler {
2323

2424
// HandleStatus renders the status dashboard
2525
func (h *DashboardHandler) HandleStatus(c *fiber.Ctx) error {
26-
tip := h.server.cm.GetTip()
27-
height := h.server.cm.GetHeight()
26+
tip := h.server.cm.GetTip(c.UserContext())
27+
height := h.server.cm.GetHeight(c.UserContext())
2828

2929
var tipHash string
3030
var tipChainwork string
@@ -36,7 +36,7 @@ func (h *DashboardHandler) HandleStatus(c *fiber.Ctx) error {
3636
tipChainwork = "N/A"
3737
}
3838

39-
network, err := h.server.cm.GetNetwork()
39+
network, err := h.server.cm.GetNetwork(c.UserContext())
4040
if err != nil {
4141
network = "unknown"
4242
}

0 commit comments

Comments
 (0)