Skip to content

Commit

Permalink
light client handler: GetLightClientOptimisticUpdate
Browse files Browse the repository at this point in the history
  • Loading branch information
nicopernas committed Oct 3, 2023
1 parent 19b06e1 commit e154a1e
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 0 deletions.
57 changes: 57 additions & 0 deletions beacon-chain/rpc/eth/light-client/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,63 @@ func (bs *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.
http2.WriteJson(w, response)
}

// GetLightClientOptimisticUpdate - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/optimistic_update.yaml
func (bs *Server) GetLightClientOptimisticUpdate(w http.ResponseWriter, req *http.Request) {
// Prepare
ctx, span := trace.StartSpan(req.Context(), "beacon.GetLightClientOptimisticUpdate")
defer span.End()

minSignatures := params.BeaconConfig().MinSyncCommitteeParticipants

block, err := bs.getLightClientEventBlock(ctx, minSignatures)
if !shared.WriteBlockFetchError(w, block, err) {
return
}

state, err := bs.Stater.StateBySlot(ctx, block.Block().Slot())
if err != nil {
http2.HandleError(w, "could not get state "+err.Error(), http.StatusInternalServerError)
return
}

// Get attested state
attestedRoot := block.Block().ParentRoot()
attestedBlock, err := bs.BeaconDB.Block(ctx, attestedRoot)
if err != nil {
http2.HandleError(w, "could not get attested block "+err.Error(), http.StatusInternalServerError)
return
}
if attestedBlock == nil {
http2.HandleError(w, "attested block is nil", http.StatusInternalServerError)
return
}

attestedSlot := attestedBlock.Block().Slot()
attestedState, err := bs.Stater.StateBySlot(ctx, attestedSlot)
if err != nil {
http2.HandleError(w, "could not get attested state "+err.Error(), http.StatusInternalServerError)
return
}

update, err := NewLightClientOptimisticUpdateFromBeaconState(
ctx,
state,
block,
attestedState,
)
if err != nil {
http2.HandleError(w, "could not get light client optimistic update "+err.Error(), http.StatusInternalServerError)
return
}

response := &LightClientUpdateWithVersion{
Version: ethpbv2.Version(attestedState.Version()).String(),
Data: update,
}

http2.WriteJson(w, response)
}

// getLightClientEventBlock - returns the block that should be used for light client events, which satisfies the minimum number of signatures from sync committee
func (bs *Server) getLightClientEventBlock(ctx context.Context, minSignaturesRequired uint64) (interfaces.ReadOnlySignedBeaconBlock, error) {
// Get the current state
Expand Down
103 changes: 103 additions & 0 deletions beacon-chain/rpc/eth/light-client/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,106 @@ func TestLightClientHandler_GetLightClientFinalityUpdate(t *testing.T) {
require.Equal(t, "CAPELLA", resp.Version)
require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp.Data.AttestedHeader.BodyRoot)
}

func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) {
helpers.ClearCache()
ctx := context.Background()
config := params.BeaconConfig()
slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)

attestedState, err := util.NewBeaconStateCapella()
require.NoError(t, err)
err = attestedState.SetSlot(slot.Sub(1))
require.NoError(t, err)

require.NoError(t, attestedState.SetFinalizedCheckpoint(&ethpb.Checkpoint{
Epoch: config.AltairForkEpoch - 10,
Root: make([]byte, 32),
}))

parent := util.NewBeaconBlockCapella()
parent.Block.Slot = slot.Sub(1)

signedParent, err := blocks.NewSignedBeaconBlock(parent)
require.NoError(t, err)

parentHeader, err := signedParent.Header()
require.NoError(t, err)
attestedHeader := parentHeader.Header

err = attestedState.SetLatestBlockHeader(attestedHeader)
require.NoError(t, err)
attestedStateRoot, err := attestedState.HashTreeRoot(ctx)
require.NoError(t, err)

// get a new signed block so the root is updated with the new state root
parent.Block.StateRoot = attestedStateRoot[:]
signedParent, err = blocks.NewSignedBeaconBlock(parent)
require.NoError(t, err)

st, err := util.NewBeaconStateCapella()
require.NoError(t, err)
err = st.SetSlot(slot)
require.NoError(t, err)

parentRoot, err := signedParent.Block().HashTreeRoot()
require.NoError(t, err)

block := util.NewBeaconBlockCapella()
block.Block.Slot = slot
block.Block.ParentRoot = parentRoot[:]

for i := uint64(0); i < config.SyncCommitteeSize; i++ {
block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true)
}

signedBlock, err := blocks.NewSignedBeaconBlock(block)
require.NoError(t, err)

h, err := signedBlock.Header()
require.NoError(t, err)

err = st.SetLatestBlockHeader(h.Header)
require.NoError(t, err)
stateRoot, err := st.HashTreeRoot(ctx)
require.NoError(t, err)

// get a new signed block so the root is updated with the new state root
block.Block.StateRoot = stateRoot[:]
signedBlock, err = blocks.NewSignedBeaconBlock(block)
require.NoError(t, err)

db := testDB.SetupDB(t)

util.SaveBlock(t, ctx, db, block)
util.SaveBlock(t, ctx, db, parent)
root, err := block.Block.HashTreeRoot()
require.NoError(t, err)

require.NoError(t, db.SaveStateSummary(ctx, &ethpb.StateSummary{Root: root[:]}))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
require.NoError(t, db.SaveState(ctx, st, root))

mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{
root: true,
}}
s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
slot.Sub(1): attestedState,
slot: st,
}},
BeaconDB: db,
HeadFetcher: mockChainService,
}
request := httptest.NewRequest("GET", "http://foo.com", nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

s.GetLightClientOptimisticUpdate(writer, request)

require.Equal(t, http.StatusOK, writer.Code)
resp := &LightClientUpdateWithVersion{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, "CAPELLA", resp.Version)
require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp.Data.AttestedHeader.BodyRoot)
}
90 changes: 90 additions & 0 deletions beacon-chain/rpc/eth/light-client/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
v1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
v2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
"github.com/prysmaticlabs/prysm/v4/time/slots"
Expand Down Expand Up @@ -229,6 +230,95 @@ func NewLightClientFinalityUpdateFromBeaconState(
return NewLightClientUpdateToJSON(result), nil
}

func NewLightClientOptimisticUpdateFromBeaconState(
ctx context.Context,
state state.BeaconState,
block interfaces.ReadOnlySignedBeaconBlock,
attestedState state.BeaconState) (*LightClientUpdate, error) {

result, err := blockchain.NewLightClientOptimisticUpdateFromBeaconState(ctx, state, block, attestedState)
if err != nil {
return nil, err
}

return NewLightClientUpdateToJSON(result), nil
}

func NewLightClientBootstrapFromJSON(bootstrapJSON *LightClientBootstrap) (*v2.LightClientBootstrap, error) {
bootstrap := &v2.LightClientBootstrap{}
var err error
if bootstrap.Header, err = headerFromJSON(bootstrapJSON.Header); err != nil {
return nil, err
}
if bootstrap.CurrentSyncCommittee, err = syncCommitteeFromJSON(bootstrapJSON.CurrentSyncCommittee); err != nil {
return nil, err
}
if bootstrap.CurrentSyncCommitteeBranch, err = branchFromJSON(bootstrapJSON.CurrentSyncCommitteeBranch); err != nil {
return nil, err
}
return bootstrap, nil
}

func headerFromJSON(headerJSON *apimiddleware.BeaconBlockHeaderJson) (*v1.BeaconBlockHeader, error) {
if headerJSON == nil {
return nil, nil
}
header := &v1.BeaconBlockHeader{}
var err error
slot, err := strconv.ParseUint(headerJSON.Slot, 10, 64)
if err != nil {
return nil, err
}
header.Slot = primitives.Slot(slot)
proposerIndex, err := strconv.ParseUint(headerJSON.ProposerIndex, 10, 64)
if err != nil {
return nil, err
}
header.ProposerIndex = primitives.ValidatorIndex(proposerIndex)
if header.ParentRoot, err = hexutil.Decode(headerJSON.ParentRoot); err != nil {
return nil, err
}
if header.StateRoot, err = hexutil.Decode(headerJSON.StateRoot); err != nil {
return nil, err
}
if header.BodyRoot, err = hexutil.Decode(headerJSON.BodyRoot); err != nil {
return nil, err
}
return header, nil
}

func syncCommitteeFromJSON(syncCommitteeJSON *apimiddleware.SyncCommitteeJson) (*v2.SyncCommittee, error) {
if syncCommitteeJSON == nil {
return nil, nil
}
syncCommittee := &v2.SyncCommittee{
Pubkeys: make([][]byte, len(syncCommitteeJSON.Pubkeys)),
}
for i, pubKey := range syncCommitteeJSON.Pubkeys {
var err error
if syncCommittee.Pubkeys[i], err = hexutil.Decode(pubKey); err != nil {
return nil, err
}
}
var err error
if syncCommittee.AggregatePubkey, err = hexutil.Decode(syncCommitteeJSON.AggregatePubkey); err != nil {
return nil, err
}
return syncCommittee, nil
}

func branchFromJSON(branch []string) ([][]byte, error) {
var branchBytes [][]byte
for _, root := range branch {
branch, err := hexutil.Decode(root)
if err != nil {
return nil, err
}
branchBytes = append(branchBytes, branch)
}
return branchBytes, nil
}

func branchToJSON(branchBytes [][]byte) []string {
if branchBytes == nil {
return nil
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/rpc/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ func (s *Service) Start() {
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/bootstrap/{block_root}", lightClientServer.GetLightClientBootstrap).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/updates", lightClientServer.GetLightClientUpdatesByRange).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/finality_update", lightClientServer.GetLightClientFinalityUpdate).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/optimistic_update", lightClientServer.GetLightClientOptimisticUpdate).Methods(http.MethodGet)

ethpbv1alpha1.RegisterNodeServer(s.grpcServer, nodeServer)
ethpbservice.RegisterBeaconNodeServer(s.grpcServer, nodeServerEth)
Expand Down

0 comments on commit e154a1e

Please sign in to comment.