Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions openapi/SwarmCommon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,25 @@ components:
type: boolean
swapEnabled:
type: boolean
beeStatus:
type: string
description: >
Current node startup or runtime phase. The zero/unknown value means
the phase has not been reported yet.
* `starting`
* `waiting_chain_sync`
* `starting_chequebook`
* `opening_localstore`
* `resetting_reserve`
* `loading_postage_snapshot`
* `syncing_postage`
* `updating_stake_height`
* `warming_up`
* `counting_reserve`
* `evicting_reserve`
* `syncing_reserve`
* `ready`
* `unknown`

HealthStatus:
type: object
Expand All @@ -521,6 +540,9 @@ components:
type: string
default: "0.0.0"
description: The default value is set in case the bee binary was not build correctly.
beeStatus:
type: string
description: Current node startup or runtime phase. See Node.beeStatus.

PostageBatch:
type: object
Expand Down Expand Up @@ -971,6 +993,9 @@ components:
type: integer
isWarmingUp:
type: boolean
beeStatus:
type: string
description: Current node startup or runtime phase. See Node.beeStatus.

StatusPeersResponse:
type: object
Expand Down
26 changes: 26 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ type Service struct {

statusService *status.Service
isWarmingUp bool
beeStatus BeeStatus
}

type BeeStatus interface {
StatusCode() int32
StatusString() string
}

func (s *Service) SetP2P(p2p p2p.DebugService) {
Expand Down Expand Up @@ -396,6 +402,26 @@ func (s *Service) SetIsWarmingUp(v bool) {
}
}

func (s *Service) SetBeeStatus(status BeeStatus) {
if s != nil {
s.beeStatus = status
}
}

func (s *Service) BeeStatusCode() int32 {
if s == nil || s.beeStatus == nil {
return 0
}
return s.beeStatus.StatusCode()
}

func (s *Service) BeeStatusString() string {
if s == nil || s.beeStatus == nil {
return "unknown"
}
return s.beeStatus.StatusString()
}

// Close hangs up running websockets on shutdown.
func (s *Service) Close() error {
s.logger.Info("api shutting down")
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ type testServerOptions struct {
ChequebookDisabled bool
SwapDisabled bool
Erc20ServiceNil bool
BeeStatus api.BeeStatus
}

func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.Conn, string, *chanStorer) {
Expand Down Expand Up @@ -231,6 +232,7 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.

s.SetSwarmAddress(&o.Overlay)
s.SetProbe(o.Probe)
s.SetBeeStatus(o.BeeStatus)

tracer := o.Tracer
if tracer == nil {
Expand Down
131 changes: 131 additions & 0 deletions pkg/api/bee_status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2026 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package api_test

import (
"crypto/ecdsa"
"net/http"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethersphere/bee/v2"
"github.com/ethersphere/bee/v2/pkg/api"
"github.com/ethersphere/bee/v2/pkg/jsonhttp"
"github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/v2/pkg/log"
)

type testBeeStatus struct {
code int32
name string
}

func (t testBeeStatus) StatusCode() int32 { return t.code }
func (t testBeeStatus) StatusString() string { return t.name }

func TestSetBeeStatus(t *testing.T) {
t.Parallel()

var unset *api.Service
if unset.BeeStatusCode() != 0 || unset.BeeStatusString() != "unknown" {
t.Fatal("nil service must report unknown status")
}

s := api.New(
ecdsa.PublicKey{},
ecdsa.PublicKey{},
common.Address{},
nil,
log.Noop,
nil,
nil,
api.FullMode,
false,
false,
nil,
nil,
nil,
)

if s.BeeStatusString() != "unknown" {
t.Fatalf("got %q, want unknown", s.BeeStatusString())
}

s.SetBeeStatus(testBeeStatus{code: 4, name: "opening_localstore"})
if s.BeeStatusCode() != 4 {
t.Fatalf("got code %d, want 4", s.BeeStatusCode())
}
if s.BeeStatusString() != "opening_localstore" {
t.Fatalf("got %q, want opening_localstore", s.BeeStatusString())
}
}

func TestBeeStatusEndpoints(t *testing.T) {
t.Parallel()

status := testBeeStatus{code: 4, name: "opening_localstore"}
probe := api.NewProbe()
probe.SetHealthy(api.ProbeStatusOK)

t.Run("available endpoints", func(t *testing.T) {
t.Parallel()

testServer, _, _, _ := newTestServer(t, testServerOptions{
BeeStatus: status,
Probe: probe,
})

jsonhttptest.Request(t, testServer, http.MethodGet, "/node", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(api.NodeResponse{
BeeMode: api.FullMode.String(),
ChequebookEnabled: true,
SwapEnabled: true,
BeeStatus: "opening_localstore",
}),
)
jsonhttptest.Request(t, testServer, http.MethodGet, "/health", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(api.HealthStatusResponse{
Status: "ok",
Version: bee.Version,
APIVersion: api.Version,
BeeStatus: "opening_localstore",
}),
)
})

t.Run("status and reservestate before full api", func(t *testing.T) {
t.Parallel()

testServer, _, _, _ := newTestServer(t, testServerOptions{
BeeStatus: status,
FullAPIDisabled: true,
})

jsonhttptest.Request(t, testServer, http.MethodGet, "/status", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(api.StatusSnapshotResponse{
Proximity: 256,
BeeMode: api.FullMode.String(),
BeeStatus: "opening_localstore",
}),
)
jsonhttptest.Request(t, testServer, http.MethodGet, "/reservestate", http.StatusOK)
})

t.Run("unavailable message", func(t *testing.T) {
t.Parallel()

testServer, _, _, _ := newTestServer(t, testServerOptions{
BeeStatus: status,
FullAPIDisabled: true,
})

jsonhttptest.Request(t, testServer, http.MethodGet, "/bytes/00", http.StatusServiceUnavailable,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Code: http.StatusServiceUnavailable,
Message: "node is not ready: opening_localstore",
}),
)
})
}
2 changes: 2 additions & 0 deletions pkg/api/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type healthStatusResponse struct {
Status string `json:"status"`
Version string `json:"version"`
APIVersion string `json:"apiVersion"`
BeeStatus string `json:"beeStatus"`
}

func (s *Service) healthHandler(w http.ResponseWriter, _ *http.Request) {
Expand All @@ -23,5 +24,6 @@ func (s *Service) healthHandler(w http.ResponseWriter, _ *http.Request) {
Status: status.String(),
Version: bee.Version,
APIVersion: Version,
BeeStatus: s.BeeStatusString(),
})
}
4 changes: 4 additions & 0 deletions pkg/api/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func TestHealth(t *testing.T) {
Status: "nok",
Version: bee.Version,
APIVersion: api.Version,
BeeStatus: "unknown",
}))
})

Expand All @@ -42,6 +43,7 @@ func TestHealth(t *testing.T) {
Status: "nok",
Version: bee.Version,
APIVersion: api.Version,
BeeStatus: "unknown",
}))

// When we set health probe to OK it should indicate that node is healthy
Expand All @@ -50,6 +52,7 @@ func TestHealth(t *testing.T) {
Status: "ok",
Version: bee.Version,
APIVersion: api.Version,
BeeStatus: "unknown",
}))

// When we set health probe to NOK it should indicate that node is not healthy
Expand All @@ -58,6 +61,7 @@ func TestHealth(t *testing.T) {
Status: "nok",
Version: bee.Version,
APIVersion: api.Version,
BeeStatus: "unknown",
}))
})
}
2 changes: 2 additions & 0 deletions pkg/api/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type nodeResponse struct {
BeeMode string `json:"beeMode"`
ChequebookEnabled bool `json:"chequebookEnabled"`
SwapEnabled bool `json:"swapEnabled"`
BeeStatus string `json:"beeStatus"`
}

func (b BeeNodeMode) String() string {
Expand All @@ -43,5 +44,6 @@ func (s *Service) nodeGetHandler(w http.ResponseWriter, _ *http.Request) {
BeeMode: s.beeMode.String(),
ChequebookEnabled: s.chequebookEnabled,
SwapEnabled: s.swapEnabled,
BeeStatus: s.BeeStatusString(),
})
}
1 change: 1 addition & 0 deletions pkg/api/p2p_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func TestAddresses(t *testing.T) {
BeeMode: api.FullMode.String(),
ChequebookEnabled: true,
SwapEnabled: true,
BeeStatus: "unknown",
}),
)
}
Expand Down
30 changes: 18 additions & 12 deletions pkg/api/postage.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,20 +431,26 @@ type chainStateResponse struct {
func (s *Service) reserveStateHandler(w http.ResponseWriter, _ *http.Request) {
logger := s.logger.WithName("get_reservestate").Build()

commitment, err := s.batchStore.Commitment()
if err != nil {
logger.Debug("batch store commitment calculation failed", "error", err)
logger.Error(nil, "batch store commitment calculation failed")
jsonhttp.InternalServerError(w, "unable to calculate commitment")
return
resp := reserveStateResponse{}

if s.batchStore != nil {
commitment, err := s.batchStore.Commitment()
if err != nil {
logger.Debug("batch store commitment calculation failed", "error", err)
logger.Error(nil, "batch store commitment calculation failed")
jsonhttp.InternalServerError(w, "unable to calculate commitment")
return
}
resp.Radius = s.batchStore.Radius()
resp.Commitment = commitment
}

jsonhttp.OK(w, reserveStateResponse{
Radius: s.batchStore.Radius(),
StorageRadius: s.storer.StorageRadius(),
Commitment: commitment,
ReserveCapacityDoubling: s.storer.CapacityDoubling(),
})
if s.storer != nil {
resp.StorageRadius = s.storer.StorageRadius()
resp.ReserveCapacityDoubling = s.storer.CapacityDoubling()
}

jsonhttp.OK(w, resp)
}

// chainStateHandler returns the current chain state.
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/readiness.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ func (s *Service) readinessHandler(w http.ResponseWriter, _ *http.Request) {
Status: "ready",
Version: bee.Version,
APIVersion: Version,
BeeStatus: s.BeeStatusString(),
})
} else {
jsonhttp.BadRequest(w, ReadyStatusResponse{
Status: "notReady",
Version: bee.Version,
APIVersion: Version,
BeeStatus: s.BeeStatusString(),
})
}
}
2 changes: 2 additions & 0 deletions pkg/api/readiness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func TestReadiness(t *testing.T) {
Status: "ready",
Version: "-dev",
APIVersion: "0.0.0",
BeeStatus: "unknown",
}))

// When we set readiness probe to NOK it should indicate that API is not ready
Expand All @@ -51,6 +52,7 @@ func TestReadiness(t *testing.T) {
Status: "notReady",
Version: "-dev",
APIVersion: "0.0.0",
BeeStatus: "unknown",
}))
})
}
Loading