diff --git a/mod/consensus/pkg/cometbft/service/abci.go b/mod/consensus/pkg/cometbft/service/abci.go index 91c600db2d..3bb8474320 100644 --- a/mod/consensus/pkg/cometbft/service/abci.go +++ b/mod/consensus/pkg/cometbft/service/abci.go @@ -36,6 +36,7 @@ import ( "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/json" math "github.com/berachain/beacon-kit/mod/primitives/pkg/math" cmtabci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/node" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkversion "github.com/cosmos/cosmos-sdk/version" @@ -615,3 +616,8 @@ func (s *Service[_]) GetBlockRetentionHeight(commitHeight int64) int64 { func (s *Service[_]) GetBeaconVersion() (string, error) { return sdkversion.Version, nil } + +// GetCometNode returns the concrete CometBFT node. +func (s *Service[_]) GetCometNode() *node.Node { + return s.node +} diff --git a/mod/node-api/backend/backend.go b/mod/node-api/backend/backend.go index 4fe05342ae..47e5958b3d 100644 --- a/mod/node-api/backend/backend.go +++ b/mod/node-api/backend/backend.go @@ -25,6 +25,7 @@ import ( "github.com/berachain/beacon-kit/mod/primitives/pkg/common" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" + "github.com/cometbft/cometbft/node" ) // Backend is the db access layer for the beacon node-api. @@ -237,3 +238,10 @@ func (b *Backend[ } return appVersion, nil } + +// GetNode returns the comet node from the backend. +func (b *Backend[ + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, +]) GetNode() *node.Node { + return b.node.GetCometNode() +} diff --git a/mod/node-api/backend/types.go b/mod/node-api/backend/types.go index ecf8de75a5..c2553da320 100644 --- a/mod/node-api/backend/types.go +++ b/mod/node-api/backend/types.go @@ -30,6 +30,7 @@ import ( "github.com/berachain/beacon-kit/mod/primitives/pkg/math" "github.com/berachain/beacon-kit/mod/primitives/pkg/transition" "github.com/berachain/beacon-kit/mod/state-transition/pkg/core" + "github.com/cometbft/cometbft/node" ) // The AvailabilityStore interface is responsible for validating and storing @@ -112,6 +113,8 @@ type Node[ContextT any] interface { CreateQueryContext(height int64, prove bool) (ContextT, error) // GetBeaconVersion returns the version of the beacon node. GetBeaconVersion() (string, error) + // GetCometNode returns the comet node. + GetCometNode() *node.Node } type StateProcessor[BeaconStateT any] interface { diff --git a/mod/node-api/handlers/node/backend.go b/mod/node-api/handlers/node/backend.go index 032d58c8a6..3888f6d766 100644 --- a/mod/node-api/handlers/node/backend.go +++ b/mod/node-api/handlers/node/backend.go @@ -20,6 +20,9 @@ package node +import "github.com/cometbft/cometbft/node" + type Backend interface { GetNodeVersion() (string, error) + GetNode() *node.Node } diff --git a/mod/node-api/handlers/node/node.go b/mod/node-api/handlers/node/node.go index 22e69ff67f..f986a00f71 100644 --- a/mod/node-api/handlers/node/node.go +++ b/mod/node-api/handlers/node/node.go @@ -21,32 +21,47 @@ package node import ( + "github.com/berachain/beacon-kit/mod/errors" nodetypes "github.com/berachain/beacon-kit/mod/node-api/handlers/node/types" "github.com/berachain/beacon-kit/mod/node-api/handlers/types" ) -// Syncing is a placeholder so that beacon API clients don't break. -// -// TODO: Implement with real data. -func (h *Handler[ContextT]) Syncing(ContextT) (any, error) { - type SyncingResponse struct { - Data struct { - HeadSlot string `json:"head_slot"` - SyncDistance string `json:"sync_distance"` - IsSyncing bool `json:"is_syncing"` - IsOptimistic bool `json:"is_optimistic"` - ELOffline bool `json:"el_offline"` - } `json:"data"` +var ( + errNilBlockStore = errors.New("block store is nil") + errNilNode = errors.New("node is nil") +) + +// Syncing returns the syncing status of the beacon node. +func (h *Handler[ContextT]) Syncing(_ ContextT) (any, error) { + node := h.backend.GetNode() + if node == nil { + return nil, errNilNode + } + + // Get blockStore for heights + blockStore := node.BlockStore() + if blockStore == nil { + return nil, errNilBlockStore } - response := SyncingResponse{} - response.Data.HeadSlot = "0" - response.Data.SyncDistance = "1" - response.Data.IsSyncing = false - response.Data.IsOptimistic = true - response.Data.ELOffline = false + latestHeight := blockStore.Height() + baseHeight := blockStore.Base() + + response := nodetypes.SyncingData{ + HeadSlot: latestHeight, + IsOptimistic: true, + ELOffline: false, + } + + // Calculate sync distance using block heights + response.SyncDistance = latestHeight - baseHeight + // If SyncDistance is greater than 0, + // we consider the node to be syncing + if response.SyncDistance > 0 { + response.IsSyncing = true + } - return response, nil + return types.Wrap(&response), nil } // Version returns the version of the beacon node. diff --git a/mod/node-api/handlers/node/types/response.go b/mod/node-api/handlers/node/types/response.go index be45ef558d..82ac614209 100644 --- a/mod/node-api/handlers/node/types/response.go +++ b/mod/node-api/handlers/node/types/response.go @@ -20,6 +20,37 @@ package types +import ( + "encoding/json" + "strconv" +) + type VersionData struct { Version string `json:"version"` } + +type SyncingData struct { + HeadSlot int64 `json:"head_slot"` + SyncDistance int64 `json:"sync_distance"` + IsSyncing bool `json:"is_syncing"` + IsOptimistic bool `json:"is_optimistic"` + ELOffline bool `json:"el_offline"` +} + +type syncingJSON struct { + HeadSlot string `json:"head_slot"` + SyncDistance string `json:"sync_distance"` + IsSyncing bool `json:"is_syncing"` + IsOptimistic bool `json:"is_optimistic"` + ELOffline bool `json:"el_offline"` +} + +func (s *SyncingData) MarshalJSON() ([]byte, error) { + return json.Marshal(syncingJSON{ + HeadSlot: strconv.FormatInt(s.HeadSlot, 10), + SyncDistance: strconv.FormatInt(s.SyncDistance, 10), + IsSyncing: s.IsSyncing, + IsOptimistic: s.IsOptimistic, + ELOffline: s.ELOffline, + }) +} diff --git a/mod/node-core/pkg/components/api.go b/mod/node-core/pkg/components/api.go index f66bc6ed4d..2cec1eaad8 100644 --- a/mod/node-core/pkg/components/api.go +++ b/mod/node-core/pkg/components/api.go @@ -29,6 +29,7 @@ import ( "github.com/berachain/beacon-kit/mod/node-api/handlers" "github.com/berachain/beacon-kit/mod/node-api/server" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" + "github.com/cometbft/cometbft/node" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -78,6 +79,7 @@ func ProvideNodeAPIBackend[ NodeT interface { CreateQueryContext(height int64, prove bool) (sdk.Context, error) GetBeaconVersion() (string, error) + GetCometNode() *node.Node }, StorageBackendT StorageBackend[ AvailabilityStoreT, BeaconStateT, BeaconBlockStoreT, DepositStoreT, diff --git a/mod/node-core/pkg/components/api_handlers.go b/mod/node-core/pkg/components/api_handlers.go index df5ef6ce1f..1e46eee731 100644 --- a/mod/node-core/pkg/components/api_handlers.go +++ b/mod/node-core/pkg/components/api_handlers.go @@ -30,6 +30,7 @@ import ( eventsapi "github.com/berachain/beacon-kit/mod/node-api/handlers/events" nodeapi "github.com/berachain/beacon-kit/mod/node-api/handlers/node" proofapi "github.com/berachain/beacon-kit/mod/node-api/handlers/proof" + "github.com/cometbft/cometbft/node" ) type NodeAPIHandlersInput[ @@ -146,6 +147,7 @@ func ProvideNodeAPIEventsHandler[ type Backend interface { GetNodeVersion() (string, error) + GetNode() *node.Node } func ProvideNodeAPINodeHandler[ diff --git a/testing/e2e/e2e_node_api_test.go b/testing/e2e/e2e_node_api_test.go index 5ee547a704..1caec97426 100644 --- a/testing/e2e/e2e_node_api_test.go +++ b/testing/e2e/e2e_node_api_test.go @@ -22,6 +22,7 @@ package e2e_test import ( beaconapi "github.com/attestantio/go-eth2-client/api" + "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/berachain/beacon-kit/testing/e2e/config" "github.com/berachain/beacon-kit/testing/e2e/suite/types" ) @@ -52,3 +53,22 @@ func (s *BeaconKitE2ESuite) TestNodeVersion() { versionStr := version.Data s.Require().NotEmpty(versionStr) } + +// TestNodeSyncing tests the node api for syncing status of the node. +func (s *BeaconKitE2ESuite) TestNodeSyncing() { + client := s.initNodeTest() + + syncing, err := client.NodeSyncing(s.Ctx(), + &beaconapi.NodeSyncingOpts{}) + s.Require().NoError(err) + s.Require().NotNil(syncing) + syncData := syncing.Data + s.Require().NotEmpty(syncData.HeadSlot) + s.Require().Greater(syncData.HeadSlot, phase0.Slot(0)) + s.Require().NotNil(syncData.SyncDistance) + + s.Require().NotNil(syncData.IsSyncing) + s.Require().True(syncData.IsOptimistic) + + // TODO: Add more assertions. +} diff --git a/testing/e2e/suite/types/consensus_client.go b/testing/e2e/suite/types/consensus_client.go index f94884e44d..5f2b5aa1ba 100644 --- a/testing/e2e/suite/types/consensus_client.go +++ b/testing/e2e/suite/types/consensus_client.go @@ -258,5 +258,15 @@ func (cc ConsensusClient) Spec( return cc.beaconClient.Spec(ctx, opts) } +func (cc ConsensusClient) NodeSyncing( + ctx context.Context, + opts *beaconapi.NodeSyncingOpts, +) (*beaconapi.Response[*apiv1.SyncState], error) { + if cc.beaconClient == nil { + return nil, errors.New("beacon client is not initialized") + } + return cc.beaconClient.NodeSyncing(ctx, opts) +} + // TODO: Add helpers for the beacon node-api client (converting from // go-eth2-client types to beacon-kit consensus types).