diff --git a/consensus/cometbft/service/abci.go b/consensus/cometbft/service/abci.go index 453950d564..9b2f1520be 100644 --- a/consensus/cometbft/service/abci.go +++ b/consensus/cometbft/service/abci.go @@ -30,6 +30,7 @@ import ( errorsmod "cosmossdk.io/errors" cmtabci "github.com/cometbft/cometbft/abci/types" abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" + "github.com/cometbft/cometbft/node" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkversion "github.com/cosmos/cosmos-sdk/version" ) @@ -234,3 +235,8 @@ func (*Service) CheckTx( ) (*abci.CheckTxResponse, error) { return &abci.CheckTxResponse{}, nil } + +// GetCometNode returns the concrete CometBFT node. +func (s *Service) GetCometNode() *node.Node { + return s.node +} diff --git a/node-api/backend/backend.go b/node-api/backend/backend.go index 69ea3c84e8..38d49b4f52 100644 --- a/node-api/backend/backend.go +++ b/node-api/backend/backend.go @@ -30,6 +30,7 @@ import ( "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/math" cmtcfg "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/node" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" ) @@ -113,3 +114,9 @@ func (b *Backend) Spec() (chain.Spec, error) { } return b.cs, nil } + +// GetNode returns the comet node from the backend. +// This is part of the NodeAPIBackend interface. +func (b *Backend) GetNode() *node.Node { + return b.node.GetCometNode() +} diff --git a/node-api/backend/backend_test.go b/node-api/backend/backend_test.go index 9a4142c8c0..68ff2463f3 100644 --- a/node-api/backend/backend_test.go +++ b/node-api/backend/backend_test.go @@ -32,6 +32,7 @@ import ( "github.com/berachain/beacon-kit/node-core/components/metrics" statedb "github.com/berachain/beacon-kit/state-transition/core/state" "github.com/berachain/beacon-kit/storage/beacondb" + "github.com/cometbft/cometbft/node" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -79,3 +80,7 @@ func (t *testConsensusService) Name() string { func (t *testConsensusService) LastBlockHeight() int64 { panic(errTestMemberNotImplemented) } + +func (t *testConsensusService) GetCometNode() *node.Node { + panic(errTestMemberNotImplemented) +} diff --git a/node-api/handlers/node/backend.go b/node-api/handlers/node/backend.go new file mode 100644 index 0000000000..f8441084c7 --- /dev/null +++ b/node-api/handlers/node/backend.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2025, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package node + +import "github.com/cometbft/cometbft/node" + +// Backend is the interface for backend of the node API. +type Backend interface { + GetNode() *node.Node +} diff --git a/node-api/handlers/node/handler.go b/node-api/handlers/node/handler.go index e64d8c84ed..5bec06f1b3 100644 --- a/node-api/handlers/node/handler.go +++ b/node-api/handlers/node/handler.go @@ -20,17 +20,23 @@ package node -import "github.com/berachain/beacon-kit/node-api/handlers" +import ( + "github.com/berachain/beacon-kit/node-api/handlers" +) +// Handler is the handler for the node API. type Handler struct { *handlers.BaseHandler + backend Backend } -func NewHandler() *Handler { +// NewHandler creates a new handler for the node API. +func NewHandler(backend Backend) *Handler { h := &Handler{ BaseHandler: handlers.NewBaseHandler( handlers.NewRouteSet(""), ), + backend: backend, } return h } diff --git a/node-api/handlers/node/sync.go b/node-api/handlers/node/sync.go new file mode 100644 index 0000000000..a65e036cbe --- /dev/null +++ b/node-api/handlers/node/sync.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2025, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package node + +import ( + "errors" + + "github.com/berachain/beacon-kit/node-api/handlers" + nodetypes "github.com/berachain/beacon-kit/node-api/handlers/node/types" +) + +var ( + errNilBlockStore = errors.New("block store is nil") + errNilNode = errors.New("node is nil") +) + +// Syncing returns the syncing status of the node. +func (h *Handler) Syncing(_ handlers.Context) (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 + } + + 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 +} diff --git a/node-api/handlers/node/types/response.go b/node-api/handlers/node/types/response.go new file mode 100644 index 0000000000..51acba84e1 --- /dev/null +++ b/node-api/handlers/node/types/response.go @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2025, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package types + +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"` +} diff --git a/node-api/handlers/node/placeholders.go b/node-api/handlers/node/version.go similarity index 67% rename from node-api/handlers/node/placeholders.go rename to node-api/handlers/node/version.go index edf0e0014b..cef01655bb 100644 --- a/node-api/handlers/node/placeholders.go +++ b/node-api/handlers/node/version.go @@ -22,32 +22,7 @@ package node import "github.com/berachain/beacon-kit/node-api/handlers" -// Syncing is a placeholder so that beacon API clients don't break. -// -// TODO: Implement with real data. -func (h *Handler) Syncing(handlers.Context) (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"` - } - - response := SyncingResponse{} - response.Data.HeadSlot = "0" - response.Data.SyncDistance = "1" - response.Data.IsSyncing = false - response.Data.IsOptimistic = true - response.Data.ELOffline = false - - return response, nil -} - // Version is a placeholder so that beacon API clients don't break. -// // TODO: Implement with real data. func (h *Handler) Version(handlers.Context) (any, error) { type VersionResponse struct { diff --git a/node-core/components/api_handlers.go b/node-core/components/api_handlers.go index 673cebb6b0..50bd4d8867 100644 --- a/node-core/components/api_handlers.go +++ b/node-core/components/api_handlers.go @@ -75,8 +75,8 @@ func ProvideNodeAPIEventsHandler() *eventsapi.Handler { return eventsapi.NewHandler() } -func ProvideNodeAPINodeHandler() *nodeapi.Handler { - return nodeapi.NewHandler() +func ProvideNodeAPINodeHandler(b NodeAPIBackend) *nodeapi.Handler { + return nodeapi.NewHandler(b) } func ProvideNodeAPIProofHandler(b NodeAPIBackend) *proofapi.Handler { diff --git a/node-core/components/interfaces.go b/node-core/components/interfaces.go index de04ec0cc0..b6c1f99700 100644 --- a/node-core/components/interfaces.go +++ b/node-core/components/interfaces.go @@ -42,6 +42,7 @@ import ( statedb "github.com/berachain/beacon-kit/state-transition/core/state" "github.com/berachain/beacon-kit/storage/block" "github.com/berachain/beacon-kit/storage/deposit" + "github.com/cometbft/cometbft/node" ) type ( @@ -499,6 +500,7 @@ type ( GetSlotByBlockRoot(root common.Root) (math.Slot, error) GetSlotByStateRoot(root common.Root) (math.Slot, error) GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) + GetNode() *node.Node NodeAPIBeaconBackend NodeAPIProofBackend diff --git a/node-core/types/node.go b/node-core/types/node.go index af7e57c968..024e9b828b 100644 --- a/node-core/types/node.go +++ b/node-core/types/node.go @@ -26,6 +26,7 @@ import ( "cosmossdk.io/store" "github.com/berachain/beacon-kit/beacon/blockchain" service "github.com/berachain/beacon-kit/node-core/services/registry" + "github.com/cometbft/cometbft/node" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -58,4 +59,5 @@ type ConsensusService interface { prove bool, ) (sdk.Context, error) LastBlockHeight() int64 + GetCometNode() *node.Node } diff --git a/testing/e2e/e2e_beacon_api_test.go b/testing/e2e/e2e_beacon_api_test.go index 5e1c344d03..bff1e7b515 100644 --- a/testing/e2e/e2e_beacon_api_test.go +++ b/testing/e2e/e2e_beacon_api_test.go @@ -934,3 +934,18 @@ func (s *BeaconKitE2ESuite) TestConfigSpec() { s.Require().Contains(specData, "INACTIVITY_PENALTY_QUOTIENT_ALTAIR") s.Require().Zero(specData["INACTIVITY_PENALTY_QUOTIENT_ALTAIR"]) } + +// TestSyncing tests querying the syncing status of the beacon node. +func (s *BeaconKitE2ESuite) TestNodeSyncing() { + client := s.initBeaconTest() + + resp, err := client.NodeSyncing(s.Ctx()) + s.Require().NoError(err) + s.Require().NotNil(resp) + s.Require().NotNil(resp.Data) + + s.Require().Equal(resp.Data.HeadSlot, phase0.Slot(0)) + s.Require().Equal(resp.Data.SyncDistance, phase0.Slot(0)) + s.Require().Equal(resp.Data.IsSyncing, false) + s.Require().Equal(resp.Data.IsOptimistic, false) +} diff --git a/testing/e2e/e2e_staking_test.go b/testing/e2e/e2e_staking_test.go index 43dafcdbb0..bd92b2f02c 100644 --- a/testing/e2e/e2e_staking_test.go +++ b/testing/e2e/e2e_staking_test.go @@ -245,7 +245,7 @@ func (s *BeaconKitE2ESuite) TestDepositRobustness() { s.Require().NoError(err) nextEpoch := chainSpec.SlotToEpoch(math.Slot(blkNum)) + 1 nextEpochBlockNum := nextEpoch.Unwrap() * chainSpec.SlotsPerEpoch() - err = s.WaitForFinalizedBlockNumber(nextEpochBlockNum + 1) + err = s.WaitForFinalizedBlockNumber(nextEpochBlockNum + 10) s.Require().NoError(err) increaseAmt := new(big.Int).Mul(depositAmountGwei, big.NewInt(int64(NumDepositsLoad/config.NumValidators))) diff --git a/testing/e2e/suite/types/consensus_client.go b/testing/e2e/suite/types/consensus_client.go index 46110a15b4..7b9799f2f9 100644 --- a/testing/e2e/suite/types/consensus_client.go +++ b/testing/e2e/suite/types/consensus_client.go @@ -262,6 +262,14 @@ func (cc ConsensusClient) BlockProposerProof( return cc.beaconClient.BlockProposerProof(ctx, timestampID) } +// Syncing returns the syncing status of the beacon node. +func (cc ConsensusClient) NodeSyncing(ctx context.Context) (*beaconapi.Response[*apiv1.SyncState], error) { + if cc.beaconClient == nil { + return nil, errors.New("beacon client is not initialized") + } + return cc.beaconClient.NodeSyncing(ctx, &beaconapi.NodeSyncingOpts{}) +} + // Commit returns the commit for a block. func (cc ConsensusClient) Commit( ctx context.Context, diff --git a/testing/simulated/simcomet.go b/testing/simulated/simcomet.go index dc6364d6c2..2c77327824 100644 --- a/testing/simulated/simcomet.go +++ b/testing/simulated/simcomet.go @@ -34,6 +34,7 @@ import ( "github.com/berachain/beacon-kit/node-core/builder" "github.com/berachain/beacon-kit/node-core/components/metrics" cmtcfg "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/node" dbm "github.com/cosmos/cosmos-db" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -87,3 +88,7 @@ func (s *SimComet) CreateQueryContext(height int64, prove bool) (sdk.Context, er func (s *SimComet) LastBlockHeight() int64 { panic("unimplemented") } + +func (s *SimComet) GetCometNode() *node.Node { + return s.Comet.GetCometNode() +}