mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
HTTP endpoint for GetChainHead (#14262)
* add getChainHead endpoint * James' review * Radek' review --------- Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
@@ -225,3 +225,19 @@ type IndividualVote struct {
|
||||
InclusionDistance string `json:"inclusion_distance"`
|
||||
InactivityScore string `json:"inactivity_score"`
|
||||
}
|
||||
|
||||
type ChainHead struct {
|
||||
HeadSlot string `json:"head_slot"`
|
||||
HeadEpoch string `json:"head_epoch"`
|
||||
HeadBlockRoot string `json:"head_block_root"`
|
||||
FinalizedSlot string `json:"finalized_slot"`
|
||||
FinalizedEpoch string `json:"finalized_epoch"`
|
||||
FinalizedBlockRoot string `json:"finalized_block_root"`
|
||||
JustifiedSlot string `json:"justified_slot"`
|
||||
JustifiedEpoch string `json:"justified_epoch"`
|
||||
JustifiedBlockRoot string `json:"justified_block_root"`
|
||||
PreviousJustifiedSlot string `json:"previous_justified_slot"`
|
||||
PreviousJustifiedEpoch string `json:"previous_justified_epoch"`
|
||||
PreviousJustifiedBlockRoot string `json:"previous_justified_block_root"`
|
||||
OptimisticStatus bool `json:"optimistic_status"`
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"beacon.go",
|
||||
"errors.go",
|
||||
"log.go",
|
||||
"service.go",
|
||||
@@ -20,6 +21,7 @@ go_library(
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/forkchoice/types:go_default_library",
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
@@ -28,6 +30,7 @@ go_library(
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
|
||||
128
beacon-chain/rpc/core/beacon.go
Normal file
128
beacon-chain/rpc/core/beacon.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
// Retrieve chain head information from the DB and the current beacon state.
|
||||
func (s *Service) ChainHead(ctx context.Context) (*ethpb.ChainHead, *RpcError) {
|
||||
headBlock, err := s.HeadFetcher.HeadBlock(ctx)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get head block"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(headBlock); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "head block of chain was nil"),
|
||||
Reason: NotFound,
|
||||
}
|
||||
}
|
||||
optimisticStatus, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get optimistic status"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
headBlockRoot, err := headBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get head block root"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
validGenesis := false
|
||||
validateCP := func(cp *ethpb.Checkpoint, name string) error {
|
||||
if bytesutil.ToBytes32(cp.Root) == params.BeaconConfig().ZeroHash && cp.Epoch == 0 {
|
||||
if validGenesis {
|
||||
return nil
|
||||
}
|
||||
// Retrieve genesis block in the event we have genesis checkpoints.
|
||||
genBlock, err := s.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil || consensusblocks.BeaconBlockIsNil(genBlock) != nil {
|
||||
return errors.New("could not get genesis block")
|
||||
}
|
||||
validGenesis = true
|
||||
return nil
|
||||
}
|
||||
b, err := s.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
|
||||
if err != nil {
|
||||
return errors.Errorf("could not get %s block: %v", name, err)
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(b); err != nil {
|
||||
return errors.Errorf("could not get %s block: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
finalizedCheckpoint := s.FinalizedFetcher.FinalizedCheckpt()
|
||||
if err := validateCP(finalizedCheckpoint, "finalized"); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not get finalized checkpoint"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
justifiedCheckpoint := s.FinalizedFetcher.CurrentJustifiedCheckpt()
|
||||
if err := validateCP(justifiedCheckpoint, "justified"); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not get current justified checkpoint"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
prevJustifiedCheckpoint := s.FinalizedFetcher.PreviousJustifiedCheckpt()
|
||||
if err := validateCP(prevJustifiedCheckpoint, "prev justified"); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not get previous justified checkpoint"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
fSlot, err := slots.EpochStart(finalizedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get epoch start slot from finalized checkpoint epoch"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
jSlot, err := slots.EpochStart(justifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get epoch start slot from justified checkpoint epoch"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
pjSlot, err := slots.EpochStart(prevJustifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get epoch start slot from prev justified checkpoint epoch"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
return ðpb.ChainHead{
|
||||
HeadSlot: headBlock.Block().Slot(),
|
||||
HeadEpoch: slots.ToEpoch(headBlock.Block().Slot()),
|
||||
HeadBlockRoot: headBlockRoot[:],
|
||||
FinalizedSlot: fSlot,
|
||||
FinalizedEpoch: finalizedCheckpoint.Epoch,
|
||||
FinalizedBlockRoot: finalizedCheckpoint.Root,
|
||||
JustifiedSlot: jSlot,
|
||||
JustifiedEpoch: justifiedCheckpoint.Epoch,
|
||||
JustifiedBlockRoot: justifiedCheckpoint.Root,
|
||||
PreviousJustifiedSlot: pjSlot,
|
||||
PreviousJustifiedEpoch: prevJustifiedCheckpoint.Epoch,
|
||||
PreviousJustifiedBlockRoot: prevJustifiedCheckpoint.Root,
|
||||
OptimisticStatus: optimisticStatus,
|
||||
}, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
opfeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/synccommittee"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
BeaconDB db.ReadOnlyDatabase
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
FinalizedFetcher blockchain.FinalizationFetcher
|
||||
GenesisTimeFetcher blockchain.TimeFetcher
|
||||
|
||||
@@ -983,6 +983,15 @@ func (s *Service) prysmBeaconEndpoints(
|
||||
handler: server.GetIndividualVotes,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/prysm/v1/beacon/chain_head",
|
||||
name: namespace + ".GetChainHead",
|
||||
middleware: []mux.MiddlewareFunc{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetChainHead,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ func Test_endpoints(t *testing.T) {
|
||||
"/prysm/v1/beacon/weak_subjectivity": {http.MethodGet},
|
||||
"/eth/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
|
||||
"/prysm/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
|
||||
"/prysm/v1/beacon/chain_head": {http.MethodGet},
|
||||
}
|
||||
|
||||
prysmNodeRoutes := map[string][]string{
|
||||
|
||||
@@ -52,10 +52,14 @@ go_test(
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
"//beacon-chain/rpc/testutil:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/state/stategen/mock:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//network/httputil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
|
||||
@@ -154,3 +154,32 @@ func (s *Server) GetIndividualVotes(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
// GetChainHead retrieves information about the head of the beacon chain from
|
||||
// the view of the beacon chain node.
|
||||
func (s *Server) GetChainHead(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetChainHead")
|
||||
defer span.End()
|
||||
|
||||
ch, rpcError := s.CoreService.ChainHead(ctx)
|
||||
if rpcError != nil {
|
||||
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
|
||||
return
|
||||
}
|
||||
response := &structs.ChainHead{
|
||||
HeadSlot: fmt.Sprintf("%d", ch.HeadSlot),
|
||||
HeadEpoch: fmt.Sprintf("%d", ch.HeadEpoch),
|
||||
HeadBlockRoot: hexutil.Encode(ch.HeadBlockRoot),
|
||||
FinalizedSlot: fmt.Sprintf("%d", ch.FinalizedSlot),
|
||||
FinalizedEpoch: fmt.Sprintf("%d", ch.FinalizedEpoch),
|
||||
FinalizedBlockRoot: hexutil.Encode(ch.FinalizedBlockRoot),
|
||||
JustifiedSlot: fmt.Sprintf("%d", ch.JustifiedSlot),
|
||||
JustifiedEpoch: fmt.Sprintf("%d", ch.JustifiedEpoch),
|
||||
JustifiedBlockRoot: hexutil.Encode(ch.JustifiedBlockRoot),
|
||||
PreviousJustifiedSlot: fmt.Sprintf("%d", ch.PreviousJustifiedSlot),
|
||||
PreviousJustifiedEpoch: fmt.Sprintf("%d", ch.PreviousJustifiedEpoch),
|
||||
PreviousJustifiedBlockRoot: hexutil.Encode(ch.PreviousJustifiedBlockRoot),
|
||||
OptimisticStatus: ch.OptimisticStatus,
|
||||
}
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
@@ -19,11 +19,15 @@ import (
|
||||
dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
mockstategen "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen/mock"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
@@ -227,11 +231,11 @@ func TestServer_GetIndividualVotes_Working(t *testing.T) {
|
||||
require.NoError(t, beaconState.SetBlockRoots(br))
|
||||
att2.Data.Target.Root = rt[:]
|
||||
att2.Data.BeaconBlockRoot = newRt[:]
|
||||
err = beaconState.AppendPreviousEpochAttestations(ð.PendingAttestation{
|
||||
err = beaconState.AppendPreviousEpochAttestations(ðpb.PendingAttestation{
|
||||
Data: att1.Data, AggregationBits: bf, InclusionDelay: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = beaconState.AppendCurrentEpochAttestations(ð.PendingAttestation{
|
||||
err = beaconState.AppendCurrentEpochAttestations(ðpb.PendingAttestation{
|
||||
Data: att2.Data, AggregationBits: bf, InclusionDelay: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -658,3 +662,211 @@ func TestServer_GetIndividualVotes_CapellaEndOfEpoch(t *testing.T) {
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
}
|
||||
|
||||
// ensures that if any of the checkpoints are zero-valued, an error will be generated without genesis being present
|
||||
func TestServer_GetChainHead_NoGenesis(t *testing.T) {
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
s, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.SetSlot(1))
|
||||
|
||||
genBlock := util.NewBeaconBlock()
|
||||
genBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'G'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, genBlock)
|
||||
gRoot, err := genBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
name string
|
||||
zeroSetter func(val *ethpb.Checkpoint) error
|
||||
}{
|
||||
{
|
||||
name: "zero-value prev justified",
|
||||
zeroSetter: s.SetPreviousJustifiedCheckpoint,
|
||||
},
|
||||
{
|
||||
name: "zero-value current justified",
|
||||
zeroSetter: s.SetCurrentJustifiedCheckpoint,
|
||||
},
|
||||
{
|
||||
name: "zero-value finalized",
|
||||
zeroSetter: s.SetFinalizedCheckpoint,
|
||||
},
|
||||
}
|
||||
finalized := ðpb.Checkpoint{Epoch: 1, Root: gRoot[:]}
|
||||
prevJustified := ðpb.Checkpoint{Epoch: 2, Root: gRoot[:]}
|
||||
justified := ðpb.Checkpoint{Epoch: 3, Root: gRoot[:]}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
require.NoError(t, s.SetPreviousJustifiedCheckpoint(prevJustified))
|
||||
require.NoError(t, s.SetCurrentJustifiedCheckpoint(justified))
|
||||
require.NoError(t, s.SetFinalizedCheckpoint(finalized))
|
||||
require.NoError(t, c.zeroSetter(ðpb.Checkpoint{Epoch: 0, Root: params.BeaconConfig().ZeroHash[:]}))
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(genBlock)
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint(),
|
||||
},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
require.StringContains(t, "could not get genesis block", writer.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead_NoFinalizedBlock(t *testing.T) {
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
bs, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, bs.SetSlot(1))
|
||||
require.NoError(t, bs.SetPreviousJustifiedCheckpoint(ðpb.Checkpoint{Epoch: 3, Root: bytesutil.PadTo([]byte{'A'}, fieldparams.RootLength)}))
|
||||
require.NoError(t, bs.SetCurrentJustifiedCheckpoint(ðpb.Checkpoint{Epoch: 2, Root: bytesutil.PadTo([]byte{'B'}, fieldparams.RootLength)}))
|
||||
require.NoError(t, bs.SetFinalizedCheckpoint(ðpb.Checkpoint{Epoch: 1, Root: bytesutil.PadTo([]byte{'C'}, fieldparams.RootLength)}))
|
||||
|
||||
genBlock := util.NewBeaconBlock()
|
||||
genBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'G'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, genBlock)
|
||||
gRoot, err := genBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(context.Background(), gRoot))
|
||||
|
||||
wsb, err := blocks.NewSignedBeaconBlock(genBlock)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: bs},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: bs.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: bs.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: bs.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
require.StringContains(t, "ould not get finalized block", writer.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead_NoHeadBlock(t *testing.T) {
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: &chainMock.ChainService{Block: nil},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusNotFound, writer.Code)
|
||||
require.StringContains(t, "head block of chain was nil", writer.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.MinimalSpecConfig())
|
||||
|
||||
db := dbTest.SetupDB(t)
|
||||
genBlock := util.NewBeaconBlock()
|
||||
genBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'G'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, genBlock)
|
||||
gRoot, err := genBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(context.Background(), gRoot))
|
||||
|
||||
finalizedBlock := util.NewBeaconBlock()
|
||||
finalizedBlock.Block.Slot = 1
|
||||
finalizedBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'A'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, finalizedBlock)
|
||||
fRoot, err := finalizedBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
justifiedBlock := util.NewBeaconBlock()
|
||||
justifiedBlock.Block.Slot = 2
|
||||
justifiedBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'B'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, justifiedBlock)
|
||||
jRoot, err := justifiedBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
prevJustifiedBlock := util.NewBeaconBlock()
|
||||
prevJustifiedBlock.Block.Slot = 3
|
||||
prevJustifiedBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'C'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, prevJustifiedBlock)
|
||||
pjRoot, err := prevJustifiedBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{
|
||||
Slot: 1,
|
||||
PreviousJustifiedCheckpoint: ðpb.Checkpoint{Epoch: 3, Root: pjRoot[:]},
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Epoch: 2, Root: jRoot[:]},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 1, Root: fRoot[:]},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot, err = slots.EpochStart(st.PreviousJustifiedCheckpoint().Epoch)
|
||||
require.NoError(t, err)
|
||||
b.Block.Slot++
|
||||
wsb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: st},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: st.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: st.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: st.PreviousJustifiedCheckpoint()},
|
||||
},
|
||||
}
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
var ch *structs.ChainHead
|
||||
err = json.NewDecoder(writer.Body).Decode(&ch)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "3", ch.PreviousJustifiedEpoch, "Unexpected PreviousJustifiedEpoch")
|
||||
assert.Equal(t, "2", ch.JustifiedEpoch, "Unexpected JustifiedEpoch")
|
||||
assert.Equal(t, "1", ch.FinalizedEpoch, "Unexpected FinalizedEpoch")
|
||||
assert.Equal(t, "24", ch.PreviousJustifiedSlot, "Unexpected PreviousJustifiedSlot")
|
||||
assert.Equal(t, "16", ch.JustifiedSlot, "Unexpected JustifiedSlot")
|
||||
assert.Equal(t, "8", ch.FinalizedSlot, "Unexpected FinalizedSlot")
|
||||
assert.DeepEqual(t, hexutil.Encode(pjRoot[:]), ch.PreviousJustifiedBlockRoot, "Unexpected PreviousJustifiedBlockRoot")
|
||||
assert.DeepEqual(t, hexutil.Encode(jRoot[:]), ch.JustifiedBlockRoot, "Unexpected JustifiedBlockRoot")
|
||||
assert.DeepEqual(t, hexutil.Encode(fRoot[:]), ch.FinalizedBlockRoot, "Unexpected FinalizedBlockRoot")
|
||||
assert.Equal(t, false, ch.OptimisticStatus)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,12 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/pagination"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filters"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -244,91 +243,9 @@ func (bs *Server) listBlocksForGenesis(ctx context.Context, _ *ethpb.ListBlocksR
|
||||
// the most recent finalized and justified slots.
|
||||
// DEPRECATED: This endpoint is superseded by the /eth/v1/beacon API endpoint
|
||||
func (bs *Server) GetChainHead(ctx context.Context, _ *emptypb.Empty) (*ethpb.ChainHead, error) {
|
||||
return bs.chainHeadRetrieval(ctx)
|
||||
}
|
||||
|
||||
// Retrieve chain head information from the DB and the current beacon state.
|
||||
func (bs *Server) chainHeadRetrieval(ctx context.Context) (*ethpb.ChainHead, error) {
|
||||
headBlock, err := bs.HeadFetcher.HeadBlock(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get head block")
|
||||
}
|
||||
optimisticStatus, err := bs.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get optimistic status")
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(headBlock); err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "Head block of chain was nil: %v", err)
|
||||
}
|
||||
headBlockRoot, err := headBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get head block root: %v", err)
|
||||
}
|
||||
|
||||
validGenesis := false
|
||||
validateCP := func(cp *ethpb.Checkpoint, name string) error {
|
||||
if bytesutil.ToBytes32(cp.Root) == params.BeaconConfig().ZeroHash && cp.Epoch == 0 {
|
||||
if validGenesis {
|
||||
return nil
|
||||
}
|
||||
// Retrieve genesis block in the event we have genesis checkpoints.
|
||||
genBlock, err := bs.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil || consensusblocks.BeaconBlockIsNil(genBlock) != nil {
|
||||
return status.Error(codes.Internal, "Could not get genesis block")
|
||||
}
|
||||
validGenesis = true
|
||||
return nil
|
||||
}
|
||||
b, err := bs.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "Could not get %s block: %v", name, err)
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(b); err != nil {
|
||||
return status.Errorf(codes.Internal, "Could not get %s block: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
finalizedCheckpoint := bs.FinalizationFetcher.FinalizedCheckpt()
|
||||
if err := validateCP(finalizedCheckpoint, "finalized"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
justifiedCheckpoint := bs.FinalizationFetcher.CurrentJustifiedCheckpt()
|
||||
if err := validateCP(justifiedCheckpoint, "justified"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prevJustifiedCheckpoint := bs.FinalizationFetcher.PreviousJustifiedCheckpt()
|
||||
if err := validateCP(prevJustifiedCheckpoint, "prev justified"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fSlot, err := slots.EpochStart(finalizedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get epoch start slot from finalized checkpoint epoch")
|
||||
}
|
||||
jSlot, err := slots.EpochStart(justifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get epoch start slot from justified checkpoint epoch")
|
||||
}
|
||||
pjSlot, err := slots.EpochStart(prevJustifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get epoch start slot from prev justified checkpoint epoch")
|
||||
}
|
||||
return ðpb.ChainHead{
|
||||
HeadSlot: headBlock.Block().Slot(),
|
||||
HeadEpoch: slots.ToEpoch(headBlock.Block().Slot()),
|
||||
HeadBlockRoot: headBlockRoot[:],
|
||||
FinalizedSlot: fSlot,
|
||||
FinalizedEpoch: finalizedCheckpoint.Epoch,
|
||||
FinalizedBlockRoot: finalizedCheckpoint.Root,
|
||||
JustifiedSlot: jSlot,
|
||||
JustifiedEpoch: justifiedCheckpoint.Epoch,
|
||||
JustifiedBlockRoot: justifiedCheckpoint.Root,
|
||||
PreviousJustifiedSlot: pjSlot,
|
||||
PreviousJustifiedEpoch: prevJustifiedCheckpoint.Epoch,
|
||||
PreviousJustifiedBlockRoot: prevJustifiedCheckpoint.Root,
|
||||
OptimisticStatus: optimisticStatus,
|
||||
}, nil
|
||||
ch, err := bs.CoreService.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(core.ErrorReasonToGRPC(err.Reason), "Could not retrieve chain head: %v", err.Err)
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
@@ -68,16 +69,19 @@ func TestServer_GetChainHead_NoGenesis(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(genBlock)
|
||||
require.NoError(t, err)
|
||||
bs := &Server{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizationFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint(),
|
||||
},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
_, err = bs.GetChainHead(context.Background(), nil)
|
||||
require.ErrorContains(t, "Could not get genesis block", err)
|
||||
require.ErrorContains(t, "could not get genesis block", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,26 +106,30 @@ func TestServer_GetChainHead_NoFinalizedBlock(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
bs := &Server{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizationFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = bs.GetChainHead(context.Background(), nil)
|
||||
require.ErrorContains(t, "Could not get finalized block", err)
|
||||
require.ErrorContains(t, "could not get finalized block", err)
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead_NoHeadBlock(t *testing.T) {
|
||||
bs := &Server{
|
||||
HeadFetcher: &chainMock.ChainService{Block: nil},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: &chainMock.ChainService{Block: nil},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
_, err := bs.GetChainHead(context.Background(), nil)
|
||||
assert.ErrorContains(t, "Head block of chain was nil", err)
|
||||
assert.ErrorContains(t, "head block of chain was nil", err)
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead(t *testing.T) {
|
||||
@@ -172,13 +180,15 @@ func TestServer_GetChainHead(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
bs := &Server{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
FinalizationFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
},
|
||||
}
|
||||
|
||||
head, err := bs.GetChainHead(context.Background(), nil)
|
||||
|
||||
@@ -215,6 +215,7 @@ func NewService(ctx context.Context, cfg *Config) *Service {
|
||||
}
|
||||
rewardFetcher := &rewards.BlockRewardService{Replayer: ch, DB: s.cfg.BeaconDB}
|
||||
coreService := &core.Service{
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||
SyncChecker: s.cfg.SyncService,
|
||||
|
||||
Reference in New Issue
Block a user