From e8d204f18b4af2736c2a40d7124a998d16132fe0 Mon Sep 17 00:00:00 2001 From: Rez Date: Mon, 13 Jan 2025 21:11:02 -0500 Subject: [PATCH] Add Debug Endpoint Co-Authored-By: Cal Bera --- consensus-types/types/state.go | 32 ++++++------- node-api/backend/state.go | 10 ++++ node-api/handlers/beacon/backend.go | 2 + node-api/handlers/beacon/types/request.go | 4 ++ node-api/handlers/beacon/types/response.go | 7 +++ node-api/handlers/debug/backend.go | 33 +++++++++++++ node-api/handlers/debug/handler.go | 14 +++++- node-api/handlers/debug/routes.go | 2 +- node-api/handlers/debug/state.go | 55 ++++++++++++++++++++++ node-api/handlers/utils/id.go | 1 + node-core/components/api_handlers.go | 4 +- node-core/components/interfaces.go | 1 + 12 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 node-api/handlers/debug/backend.go create mode 100644 node-api/handlers/debug/state.go diff --git a/consensus-types/types/state.go b/consensus-types/types/state.go index 2ebe174fb5..cfdfe45dc7 100644 --- a/consensus-types/types/state.go +++ b/consensus-types/types/state.go @@ -30,34 +30,34 @@ import ( // BeaconState represents the entire state of the beacon chain. type BeaconState struct { // Versioning - GenesisValidatorsRoot common.Root - Slot math.Slot - Fork *Fork + GenesisValidatorsRoot common.Root `json:"genesis_validators_root,omitempty"` + Slot math.Slot `json:"slot,omitempty"` + Fork *Fork `json:"fork,omitempty"` // History - LatestBlockHeader *BeaconBlockHeader - BlockRoots []common.Root - StateRoots []common.Root + LatestBlockHeader *BeaconBlockHeader `json:"latest_block_header,omitempty"` + BlockRoots []common.Root `json:"block_roots,omitempty"` + StateRoots []common.Root `json:"state_roots,omitempty"` // Eth1 - Eth1Data *Eth1Data - Eth1DepositIndex uint64 - LatestExecutionPayloadHeader *ExecutionPayloadHeader + Eth1Data *Eth1Data `json:"eth1_data,omitempty"` + Eth1DepositIndex uint64 `json:"eth1_deposit_index,omitempty"` + LatestExecutionPayloadHeader *ExecutionPayloadHeader `json:"latest_execution_payload_header,omitempty"` // Registry - Validators []*Validator - Balances []uint64 + Validators []*Validator `json:"validators,omitempty"` + Balances []uint64 `json:"balances,omitempty"` // Randomness - RandaoMixes []common.Bytes32 + RandaoMixes []common.Bytes32 `json:"randao_mixes,omitempty"` // Withdrawals - NextWithdrawalIndex uint64 - NextWithdrawalValidatorIndex math.ValidatorIndex + NextWithdrawalIndex uint64 `json:"next_withdrawal_index,omitempty"` + NextWithdrawalValidatorIndex math.ValidatorIndex `json:"next_withdrawal_validator_index,omitempty"` // Slashing - Slashings []math.Gwei - TotalSlashing math.Gwei + Slashings []math.Gwei `json:"slashings,omitempty"` + TotalSlashing math.Gwei `json:"total_slashing,omitempty"` } /* -------------------------------------------------------------------------- */ diff --git a/node-api/backend/state.go b/node-api/backend/state.go index 306c6e6b9c..c27f194537 100644 --- a/node-api/backend/state.go +++ b/node-api/backend/state.go @@ -46,6 +46,16 @@ func (b Backend) StateRootAtSlot(slot math.Slot) (common.Root, error) { return st.StateRootAtIndex(slot.Unwrap() % b.cs.SlotsPerHistoricalRoot()) } +// StateAtSlot returns the beacon state at a particular slot. +func (b Backend) StateAtSlot(slot math.Slot) (*statedb.StateDB, error) { + st, _, err := b.stateFromSlot(slot) + if err != nil { + return nil, err + } + + return st, nil +} + // GetStateFork returns the fork of the state at the given stateID. func (b Backend) StateForkAtSlot(slot math.Slot) (*ctypes.Fork, error) { var fork *ctypes.Fork diff --git a/node-api/handlers/beacon/backend.go b/node-api/handlers/beacon/backend.go index 5c8d788b46..c58473322c 100644 --- a/node-api/handlers/beacon/backend.go +++ b/node-api/handlers/beacon/backend.go @@ -25,6 +25,7 @@ import ( "github.com/berachain/beacon-kit/node-api/handlers/beacon/types" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/math" + statedb "github.com/berachain/beacon-kit/state-transition/core/state" ) // Backend is the interface for backend of the beacon API. @@ -68,6 +69,7 @@ type BlockBackend interface { type StateBackend interface { StateRootAtSlot(slot math.Slot) (common.Root, error) StateForkAtSlot(slot math.Slot) (*ctypes.Fork, error) + StateAtSlot(slot math.Slot) (*statedb.StateDB, error) } type ValidatorBackend interface { diff --git a/node-api/handlers/beacon/types/request.go b/node-api/handlers/beacon/types/request.go index 6e44346aeb..7f4efe0965 100644 --- a/node-api/handlers/beacon/types/request.go +++ b/node-api/handlers/beacon/types/request.go @@ -31,6 +31,10 @@ type GetStateRootRequest struct { types.StateIDRequest } +type GetStateRequest struct { + types.StateIDRequest +} + type GetStateForkRequest struct { types.StateIDRequest } diff --git a/node-api/handlers/beacon/types/response.go b/node-api/handlers/beacon/types/response.go index 96b523729b..6801b59faf 100644 --- a/node-api/handlers/beacon/types/response.go +++ b/node-api/handlers/beacon/types/response.go @@ -36,6 +36,13 @@ type BlockResponse struct { ValidatorResponse } +type StateResponse struct { + Version string `json:"version"` + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` + Data any `json:"data"` +} + type BlockHeaderResponse struct { Root common.Root `json:"root"` Canonical bool `json:"canonical"` diff --git a/node-api/handlers/debug/backend.go b/node-api/handlers/debug/backend.go new file mode 100644 index 0000000000..aa830ae474 --- /dev/null +++ b/node-api/handlers/debug/backend.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, 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 debug + +import ( + "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/math" + statedb "github.com/berachain/beacon-kit/state-transition/core/state" +) + +// Backend is the interface for backend of the debug API. +type Backend interface { + GetSlotByStateRoot(root common.Root) (math.Slot, error) + StateAtSlot(slot math.Slot) (*statedb.StateDB, error) +} diff --git a/node-api/handlers/debug/handler.go b/node-api/handlers/debug/handler.go index 5196081ddb..58cb4756ba 100644 --- a/node-api/handlers/debug/handler.go +++ b/node-api/handlers/debug/handler.go @@ -25,15 +25,25 @@ import ( "github.com/berachain/beacon-kit/node-api/server/context" ) -type Handler[ContextT context.Context] struct { +// Handler is the handler for the beacon API. +type Handler[ + ContextT context.Context, +] struct { *handlers.BaseHandler[ContextT] + backend Backend } -func NewHandler[ContextT context.Context]() *Handler[ContextT] { +// NewHandler creates a new handler for the beacon API. +func NewHandler[ + ContextT context.Context, +]( + backend Backend, +) *Handler[ContextT] { h := &Handler[ContextT]{ BaseHandler: handlers.NewBaseHandler( handlers.NewRouteSet[ContextT](""), ), + backend: backend, } return h } diff --git a/node-api/handlers/debug/routes.go b/node-api/handlers/debug/routes.go index 2d959e387e..b871efa703 100644 --- a/node-api/handlers/debug/routes.go +++ b/node-api/handlers/debug/routes.go @@ -35,7 +35,7 @@ func (h *Handler[ContextT]) RegisterRoutes( { Method: http.MethodGet, Path: "/eth/v2/debug/beacon/states/:state_id", - Handler: h.NotImplemented, + Handler: h.GetState, }, { Method: http.MethodGet, diff --git a/node-api/handlers/debug/state.go b/node-api/handlers/debug/state.go new file mode 100644 index 0000000000..9f8f61ec25 --- /dev/null +++ b/node-api/handlers/debug/state.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, 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 debug + +import ( + beacontypes "github.com/berachain/beacon-kit/node-api/handlers/beacon/types" + "github.com/berachain/beacon-kit/node-api/handlers/utils" +) + +func (h *Handler[ContextT]) GetState(c ContextT) (any, error) { + req, err := utils.BindAndValidate[beacontypes.GetStateRequest]( + c, h.Logger(), + ) + if err != nil { + return nil, err + } + slot, err := utils.SlotFromStateID(req.StateID, h.backend) + if err != nil { + return nil, err + } + state, err := h.backend.StateAtSlot(slot) + if err != nil { + return nil, err + } + beaconState, err := state.GetMarshallable() + if err != nil { + return nil, err + } + return beacontypes.StateResponse{ + // TODO: The version should be retrieved based on the slot + Version: "deneb", // stubbed + ExecutionOptimistic: false, // stubbed + // TODO: We can set to finalized if this is less than the highest height + Finalized: false, // stubbed + Data: beaconState, + }, nil +} diff --git a/node-api/handlers/utils/id.go b/node-api/handlers/utils/id.go index 59dc94be9b..a26647f7b1 100644 --- a/node-api/handlers/utils/id.go +++ b/node-api/handlers/utils/id.go @@ -118,6 +118,7 @@ func U64FromString(id string) (math.U64, error) { } // slotFromStateID returns a slot number from the given state ID. +// TODO: This pattern does not allow us to query block 0. Genesis points to block 1. func slotFromStateID(id string) (math.Slot, error) { switch id { case StateIDFinalized, StateIDJustified, StateIDHead: diff --git a/node-core/components/api_handlers.go b/node-core/components/api_handlers.go index ec21967e1b..83581a2843 100644 --- a/node-core/components/api_handlers.go +++ b/node-core/components/api_handlers.go @@ -81,8 +81,8 @@ func ProvideNodeAPIConfigHandler[ func ProvideNodeAPIDebugHandler[ NodeAPIContextT NodeAPIContext, -]() *debugapi.Handler[NodeAPIContextT] { - return debugapi.NewHandler[NodeAPIContextT]() +](b NodeAPIBackend) *debugapi.Handler[NodeAPIContextT] { + return debugapi.NewHandler[NodeAPIContextT](b) } func ProvideNodeAPIEventsHandler[ diff --git a/node-core/components/interfaces.go b/node-core/components/interfaces.go index da2c806752..d7fcd07f08 100644 --- a/node-core/components/interfaces.go +++ b/node-core/components/interfaces.go @@ -797,6 +797,7 @@ type ( StateRootAtSlot(slot math.Slot) (common.Root, error) StateForkAtSlot(slot math.Slot) (*ctypes.Fork, error) StateFromSlotForProof(slot math.Slot) (*statedb.StateDB, math.Slot, error) + StateAtSlot(slot math.Slot) (*statedb.StateDB, error) } ValidatorBackend interface {