From b8c5af665f9f85abb518bbacecf81e0012c22b15 Mon Sep 17 00:00:00 2001 From: qinlz2 <103775174+qinlz2@users.noreply.github.com> Date: Fri, 12 Jan 2024 02:38:59 +0800 Subject: [PATCH] [3/5] light client events (#13225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add http streaming light client events * expose ForkChoiceStore * return error in insertFinalizedDeposits * send light client updates * Revert "return error in insertFinalizedDeposits" This reverts commit f7068663b8c8b3a3bf45950d5258011a5e4d803e. * fix: lint * fix: patch the wrong error response * refactor: rename the JSON structs * fix: LC finalized stream return correct format * fix: LC op stream return correct JSON format * fix: omit nil JSON fields * chore: gazzle * fix: make update by range return list directly based on spec * chore: remove unneccessary json annotations * chore: adjust comments * feat: introduce EnableLightClientEvents feature flag * feat: use enable-lightclient-events flag * chore: more logging details * chore: fix rebase errors * chore: adjust data structure to save mem * Update beacon-chain/blockchain/process_block.go Co-authored-by: Radosław Kapka * refactor: rename config EnableLightClient * refactor: rename feature flag * refactor: move helper functions to helper pkg * test: fix broken unit tests --------- Co-authored-by: Nicolás Pernas Maradei Co-authored-by: Radosław Kapka --- beacon-chain/blockchain/chain_info.go | 9 +- beacon-chain/blockchain/process_block.go | 5 +- .../blockchain/process_block_helpers.go | 129 +++++++++++++++++- beacon-chain/blockchain/service.go | 41 +++--- beacon-chain/core/feed/state/events.go | 4 + beacon-chain/rpc/eth/events/BUILD.bazel | 1 + beacon-chain/rpc/eth/events/events.go | 102 ++++++++++++-- beacon-chain/rpc/eth/events/structs.go | 24 ++++ beacon-chain/rpc/eth/light-client/handlers.go | 9 +- .../rpc/eth/light-client/handlers_test.go | 51 ++++--- beacon-chain/rpc/eth/light-client/helpers.go | 7 +- beacon-chain/rpc/eth/light-client/structs.go | 8 +- config/features/config.go | 10 +- config/features/flags.go | 5 + 14 files changed, 328 insertions(+), 77 deletions(-) diff --git a/beacon-chain/blockchain/chain_info.go b/beacon-chain/blockchain/chain_info.go index da1fcd213a..be4682b820 100644 --- a/beacon-chain/blockchain/chain_info.go +++ b/beacon-chain/blockchain/chain_info.go @@ -6,7 +6,10 @@ import ( "time" "github.com/pkg/errors" + "go.opencensus.io/trace" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers" + f "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice" doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree" forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" @@ -18,7 +21,6 @@ import ( "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v4/time/slots" - "go.opencensus.io/trace" ) // ChainInfoFetcher defines a common interface for methods in blockchain service which @@ -334,6 +336,11 @@ func (s *Service) HeadValidatorIndexToPublicKey(_ context.Context, index primiti return v.PublicKey(), nil } +// ForkChoicer returns the forkchoice interface. +func (s *Service) ForkChoicer() f.ForkChoicer { + return s.cfg.ForkChoiceStore +} + // IsOptimistic returns true if the current head is optimistic. func (s *Service) IsOptimistic(_ context.Context) (bool, error) { if slots.ToEpoch(s.CurrentSlot()) < params.BeaconConfig().BellatrixForkEpoch { diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go index 6f677b2ce8..824f213f77 100644 --- a/beacon-chain/blockchain/process_block.go +++ b/beacon-chain/blockchain/process_block.go @@ -6,6 +6,8 @@ import ( "time" "github.com/pkg/errors" + "go.opencensus.io/trace" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed" statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state" @@ -29,7 +31,6 @@ import ( "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/attestation" "github.com/prysmaticlabs/prysm/v4/runtime/version" "github.com/prysmaticlabs/prysm/v4/time/slots" - "go.opencensus.io/trace" ) // A custom slot deadline for processing state slots in our cache. @@ -67,6 +68,7 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { fcuArgs := &fcuConfig{} defer s.handleSecondFCUCall(cfg, fcuArgs) + defer s.sendLightClientFeeds(cfg) defer s.sendStateFeedOnBlock(cfg) defer reportProcessingTime(startTime) defer reportAttestationInclusion(cfg.signed.Block()) @@ -102,6 +104,7 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { if err := s.sendFCU(cfg, fcuArgs); err != nil { return errors.Wrap(err, "could not send FCU to engine") } + return nil } diff --git a/beacon-chain/blockchain/process_block_helpers.go b/beacon-chain/blockchain/process_block_helpers.go index ad214fbc7b..bfbc407da8 100644 --- a/beacon-chain/blockchain/process_block_helpers.go +++ b/beacon-chain/blockchain/process_block_helpers.go @@ -7,21 +7,24 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "go.opencensus.io/trace" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed" statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition" doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree" forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v4/config/features" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" mathutil "github.com/prysmaticlabs/prysm/v4/math" + ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v4/time/slots" - "github.com/sirupsen/logrus" - "go.opencensus.io/trace" ) // CurrentSlot returns the current slot based on time. @@ -106,6 +109,128 @@ func (s *Service) sendStateFeedOnBlock(cfg *postBlockProcessConfig) { }) } +// sendLightClientFeeds sends the light client feeds when feature flag is enabled. +func (s *Service) sendLightClientFeeds(cfg *postBlockProcessConfig) { + if features.Get().EnableLightClient { + if _, err := s.sendLightClientOptimisticUpdate(cfg.ctx, cfg.signed, cfg.postState); err != nil { + log.WithError(err).Error("Failed to send light client optimistic update") + } + + // Get the finalized checkpoint + finalized := s.ForkChoicer().FinalizedCheckpoint() + + // LightClientFinalityUpdate needs super majority + s.tryPublishLightClientFinalityUpdate(cfg.ctx, cfg.signed, finalized, cfg.postState) + } +} + +func (s *Service) tryPublishLightClientFinalityUpdate(ctx context.Context, signed interfaces.ReadOnlySignedBeaconBlock, finalized *forkchoicetypes.Checkpoint, postState state.BeaconState) { + if finalized.Epoch <= s.lastPublishedLightClientEpoch { + return + } + + config := params.BeaconConfig() + if finalized.Epoch < config.AltairForkEpoch { + return + } + + syncAggregate, err := signed.Block().Body().SyncAggregate() + if err != nil || syncAggregate == nil { + return + } + + // LightClientFinalityUpdate needs super majority + if syncAggregate.SyncCommitteeBits.Count()*3 < config.SyncCommitteeSize*2 { + return + } + + _, err = s.sendLightClientFinalityUpdate(ctx, signed, postState) + if err != nil { + log.WithError(err).Error("Failed to send light client finality update") + } else { + s.lastPublishedLightClientEpoch = finalized.Epoch + } +} + +// sendLightClientFinalityUpdate sends a light client finality update notification to the state feed. +func (s *Service) sendLightClientFinalityUpdate(ctx context.Context, signed interfaces.ReadOnlySignedBeaconBlock, + postState state.BeaconState) (int, error) { + // Get attested state + attestedRoot := signed.Block().ParentRoot() + attestedState, err := s.cfg.StateGen.StateByRoot(ctx, attestedRoot) + if err != nil { + return 0, errors.Wrap(err, "could not get attested state") + } + + // Get finalized block + var finalizedBlock interfaces.ReadOnlySignedBeaconBlock + finalizedCheckPoint := attestedState.FinalizedCheckpoint() + if finalizedCheckPoint != nil { + finalizedRoot := bytesutil.ToBytes32(finalizedCheckPoint.Root) + finalizedBlock, err = s.cfg.BeaconDB.Block(ctx, finalizedRoot) + if err != nil { + finalizedBlock = nil + } + } + + update, err := NewLightClientFinalityUpdateFromBeaconState( + ctx, + postState, + signed, + attestedState, + finalizedBlock, + ) + + if err != nil { + return 0, errors.Wrap(err, "could not create light client update") + } + + // Return the result + result := ðpbv2.LightClientFinalityUpdateWithVersion{ + Version: ethpbv2.Version(signed.Version()), + Data: CreateLightClientFinalityUpdate(update), + } + + // Send event + return s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ + Type: statefeed.LightClientFinalityUpdate, + Data: result, + }), nil +} + +// sendLightClientOptimisticUpdate sends a light client optimistic update notification to the state feed. +func (s *Service) sendLightClientOptimisticUpdate(ctx context.Context, signed interfaces.ReadOnlySignedBeaconBlock, + postState state.BeaconState) (int, error) { + // Get attested state + attestedRoot := signed.Block().ParentRoot() + attestedState, err := s.cfg.StateGen.StateByRoot(ctx, attestedRoot) + if err != nil { + return 0, errors.Wrap(err, "could not get attested state") + } + + update, err := NewLightClientOptimisticUpdateFromBeaconState( + ctx, + postState, + signed, + attestedState, + ) + + if err != nil { + return 0, errors.Wrap(err, "could not create light client update") + } + + // Return the result + result := ðpbv2.LightClientOptimisticUpdateWithVersion{ + Version: ethpbv2.Version(signed.Version()), + Data: CreateLightClientOptimisticUpdate(update), + } + + return s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ + Type: statefeed.LightClientOptimisticUpdate, + Data: result, + }), nil +} + // updateCachesPostBlockProcessing updates the next slot cache and handles the epoch // boundary in order to compute the right proposer indices after processing // state transition. This function is called on late blocks while still locked, diff --git a/beacon-chain/blockchain/service.go b/beacon-chain/blockchain/service.go index 33ada77306..1825d90094 100644 --- a/beacon-chain/blockchain/service.go +++ b/beacon-chain/blockchain/service.go @@ -11,6 +11,8 @@ import ( "time" "github.com/pkg/errors" + "go.opencensus.io/trace" + "github.com/prysmaticlabs/prysm/v4/async/event" "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/kzg" "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache" @@ -37,34 +39,35 @@ import ( "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" prysmTime "github.com/prysmaticlabs/prysm/v4/time" "github.com/prysmaticlabs/prysm/v4/time/slots" - "go.opencensus.io/trace" ) // Service represents a service that handles the internal // logic of managing the full PoS beacon chain. type Service struct { - cfg *config - ctx context.Context - cancel context.CancelFunc - genesisTime time.Time - head *head - headLock sync.RWMutex - originBlockRoot [32]byte // genesis root, or weak subjectivity checkpoint root, depending on how the node is initialized - boundaryRoots [][32]byte - checkpointStateCache *cache.CheckpointStateCache - initSyncBlocks map[[32]byte]interfaces.ReadOnlySignedBeaconBlock - initSyncBlocksLock sync.RWMutex - wsVerifier *WeakSubjectivityVerifier - clockSetter startup.ClockSetter - clockWaiter startup.ClockWaiter - syncComplete chan struct{} - blobNotifiers *blobNotifierMap - blockBeingSynced *currentlySyncingBlock - blobStorage *filesystem.BlobStorage + cfg *config + ctx context.Context + cancel context.CancelFunc + genesisTime time.Time + head *head + headLock sync.RWMutex + originBlockRoot [32]byte // genesis root, or weak subjectivity checkpoint root, depending on how the node is initialized + boundaryRoots [][32]byte + checkpointStateCache *cache.CheckpointStateCache + initSyncBlocks map[[32]byte]interfaces.ReadOnlySignedBeaconBlock + initSyncBlocksLock sync.RWMutex + wsVerifier *WeakSubjectivityVerifier + clockSetter startup.ClockSetter + clockWaiter startup.ClockWaiter + syncComplete chan struct{} + blobNotifiers *blobNotifierMap + blockBeingSynced *currentlySyncingBlock + blobStorage *filesystem.BlobStorage + lastPublishedLightClientEpoch primitives.Epoch } // config options for the service. diff --git a/beacon-chain/core/feed/state/events.go b/beacon-chain/core/feed/state/events.go index c15923eeed..b9e16a14ba 100644 --- a/beacon-chain/core/feed/state/events.go +++ b/beacon-chain/core/feed/state/events.go @@ -27,6 +27,10 @@ const ( NewHead // MissedSlot is sent when we need to notify users that a slot was missed. MissedSlot + // LightClientFinalityUpdate event + LightClientFinalityUpdate + // LightClientOptimisticUpdate event + LightClientOptimisticUpdate ) // BlockProcessedData is the data sent with BlockProcessed events. diff --git a/beacon-chain/rpc/eth/events/BUILD.bazel b/beacon-chain/rpc/eth/events/BUILD.bazel index dc7be32fc5..449cce79d4 100644 --- a/beacon-chain/rpc/eth/events/BUILD.bazel +++ b/beacon-chain/rpc/eth/events/BUILD.bazel @@ -20,6 +20,7 @@ go_library( "//beacon-chain/rpc/eth/shared:go_default_library", "//network/httputil:go_default_library", "//proto/eth/v1:go_default_library", + "//proto/eth/v2:go_default_library", "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", diff --git a/beacon-chain/rpc/eth/events/events.go b/beacon-chain/rpc/eth/events/events.go index 07b466f5b5..dc5c3c98a9 100644 --- a/beacon-chain/rpc/eth/events/events.go +++ b/beacon-chain/rpc/eth/events/events.go @@ -7,6 +7,9 @@ import ( "net/http" "github.com/ethereum/go-ethereum/common/hexutil" + log "github.com/sirupsen/logrus" + "go.opencensus.io/trace" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/operation" @@ -17,10 +20,9 @@ import ( "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" "github.com/prysmaticlabs/prysm/v4/network/httputil" ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1" + ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2" "github.com/prysmaticlabs/prysm/v4/runtime/version" "github.com/prysmaticlabs/prysm/v4/time/slots" - log "github.com/sirupsen/logrus" - "go.opencensus.io/trace" ) const ( @@ -48,6 +50,10 @@ const ( ProposerSlashingTopic = "proposer_slashing" // AttesterSlashingTopic represents a new attester slashing event topic AttesterSlashingTopic = "attester_slashing" + // LightClientFinalityUpdateTopic represents a new light client finality update event topic. + LightClientFinalityUpdateTopic = "light_client_finality_update" + // LightClientOptimisticUpdateTopic represents a new light client optimistic update event topic. + LightClientOptimisticUpdateTopic = "light_client_optimistic_update" ) const topicDataMismatch = "Event data type %T does not correspond to event topic %s" @@ -55,18 +61,20 @@ const topicDataMismatch = "Event data type %T does not correspond to event topic const chanBuffer = 1000 var casesHandled = map[string]bool{ - HeadTopic: true, - BlockTopic: true, - AttestationTopic: true, - VoluntaryExitTopic: true, - FinalizedCheckpointTopic: true, - ChainReorgTopic: true, - SyncCommitteeContributionTopic: true, - BLSToExecutionChangeTopic: true, - PayloadAttributesTopic: true, - BlobSidecarTopic: true, - ProposerSlashingTopic: true, - AttesterSlashingTopic: true, + HeadTopic: true, + BlockTopic: true, + AttestationTopic: true, + VoluntaryExitTopic: true, + FinalizedCheckpointTopic: true, + ChainReorgTopic: true, + SyncCommitteeContributionTopic: true, + BLSToExecutionChangeTopic: true, + PayloadAttributesTopic: true, + BlobSidecarTopic: true, + ProposerSlashingTopic: true, + AttesterSlashingTopic: true, + LightClientFinalityUpdateTopic: true, + LightClientOptimisticUpdateTopic: true, } // StreamEvents provides an endpoint to subscribe to the beacon node Server-Sent-Events stream. @@ -262,6 +270,72 @@ func (s *Server) handleStateEvents(ctx context.Context, w http.ResponseWriter, f ExecutionOptimistic: checkpointData.ExecutionOptimistic, } send(w, flusher, FinalizedCheckpointTopic, checkpoint) + case statefeed.LightClientFinalityUpdate: + if _, ok := requestedTopics[LightClientFinalityUpdateTopic]; !ok { + return + } + updateData, ok := event.Data.(*ethpbv2.LightClientFinalityUpdateWithVersion) + if !ok { + write(w, flusher, topicDataMismatch, event.Data, LightClientFinalityUpdateTopic) + return + } + + var finalityBranch []string + for _, b := range updateData.Data.FinalityBranch { + finalityBranch = append(finalityBranch, hexutil.Encode(b)) + } + update := &LightClientFinalityUpdateEvent{ + Version: version.String(int(updateData.Version)), + Data: &LightClientFinalityUpdate{ + AttestedHeader: &shared.BeaconBlockHeader{ + Slot: fmt.Sprintf("%d", updateData.Data.AttestedHeader.Slot), + ProposerIndex: fmt.Sprintf("%d", updateData.Data.AttestedHeader.ProposerIndex), + ParentRoot: hexutil.Encode(updateData.Data.AttestedHeader.ParentRoot), + StateRoot: hexutil.Encode(updateData.Data.AttestedHeader.StateRoot), + BodyRoot: hexutil.Encode(updateData.Data.AttestedHeader.BodyRoot), + }, + FinalizedHeader: &shared.BeaconBlockHeader{ + Slot: fmt.Sprintf("%d", updateData.Data.FinalizedHeader.Slot), + ProposerIndex: fmt.Sprintf("%d", updateData.Data.FinalizedHeader.ProposerIndex), + ParentRoot: hexutil.Encode(updateData.Data.FinalizedHeader.ParentRoot), + StateRoot: hexutil.Encode(updateData.Data.FinalizedHeader.StateRoot), + }, + FinalityBranch: finalityBranch, + SyncAggregate: &shared.SyncAggregate{ + SyncCommitteeBits: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeSignature), + }, + SignatureSlot: fmt.Sprintf("%d", updateData.Data.SignatureSlot), + }, + } + send(w, flusher, LightClientFinalityUpdateTopic, update) + case statefeed.LightClientOptimisticUpdate: + if _, ok := requestedTopics[LightClientOptimisticUpdateTopic]; !ok { + return + } + updateData, ok := event.Data.(*ethpbv2.LightClientOptimisticUpdateWithVersion) + if !ok { + write(w, flusher, topicDataMismatch, event.Data, LightClientOptimisticUpdateTopic) + return + } + update := &LightClientOptimisticUpdateEvent{ + Version: version.String(int(updateData.Version)), + Data: &LightClientOptimisticUpdate{ + AttestedHeader: &shared.BeaconBlockHeader{ + Slot: fmt.Sprintf("%d", updateData.Data.AttestedHeader.Slot), + ProposerIndex: fmt.Sprintf("%d", updateData.Data.AttestedHeader.ProposerIndex), + ParentRoot: hexutil.Encode(updateData.Data.AttestedHeader.ParentRoot), + StateRoot: hexutil.Encode(updateData.Data.AttestedHeader.StateRoot), + BodyRoot: hexutil.Encode(updateData.Data.AttestedHeader.BodyRoot), + }, + SyncAggregate: &shared.SyncAggregate{ + SyncCommitteeBits: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeSignature), + }, + SignatureSlot: fmt.Sprintf("%d", updateData.Data.SignatureSlot), + }, + } + send(w, flusher, LightClientOptimisticUpdateTopic, update) case statefeed.Reorg: if _, ok := requestedTopics[ChainReorgTopic]; !ok { return diff --git a/beacon-chain/rpc/eth/events/structs.go b/beacon-chain/rpc/eth/events/structs.go index 81e10edd41..e66327d26e 100644 --- a/beacon-chain/rpc/eth/events/structs.go +++ b/beacon-chain/rpc/eth/events/structs.go @@ -92,3 +92,27 @@ type BlobSidecarEvent struct { KzgCommitment string `json:"kzg_commitment"` VersionedHash string `json:"versioned_hash"` } + +type LightClientFinalityUpdateEvent struct { + Version string `json:"version"` + Data *LightClientFinalityUpdate `json:"data"` +} + +type LightClientFinalityUpdate struct { + AttestedHeader *shared.BeaconBlockHeader `json:"attested_header"` + FinalizedHeader *shared.BeaconBlockHeader `json:"finalized_header"` + FinalityBranch []string `json:"finality_branch"` + SyncAggregate *shared.SyncAggregate `json:"sync_aggregate"` + SignatureSlot string `json:"signature_slot"` +} + +type LightClientOptimisticUpdateEvent struct { + Version string `json:"version"` + Data *LightClientOptimisticUpdate `json:"data"` +} + +type LightClientOptimisticUpdate struct { + AttestedHeader *shared.BeaconBlockHeader `json:"attested_header"` + SyncAggregate *shared.SyncAggregate `json:"sync_aggregate"` + SignatureSlot string `json:"signature_slot"` +} diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 0bee138cc9..30beb9318a 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -8,9 +8,10 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/gorilla/mux" - "github.com/wealdtech/go-bytesutil" "go.opencensus.io/trace" + "github.com/wealdtech/go-bytesutil" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" "github.com/prysmaticlabs/prysm/v4/config/params" @@ -219,11 +220,7 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R return } - response := &LightClientUpdatesByRangeResponse{ - Updates: updates, - } - - httputil.WriteJson(w, response) + httputil.WriteJson(w, updates) } // GetLightClientFinalityUpdate - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/finality_update.yaml diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go index 9ce75cf817..c66e50bb8a 100644 --- a/beacon-chain/rpc/eth/light-client/handlers_test.go +++ b/beacon-chain/rpc/eth/light-client/handlers_test.go @@ -171,12 +171,12 @@ func TestLightClientHandler_GetLightClientUpdatesByRange(t *testing.T) { s.GetLightClientUpdatesByRange(writer, request) require.Equal(t, http.StatusOK, writer.Code) - resp := &LightClientUpdatesByRangeResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "capella", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp.Updates[0].Data.AttestedHeader.BodyRoot) - require.NotNil(t, resp.Updates) + var resp []LightClientUpdateWithVersion + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.Equal(t, 1, len(resp)) + require.Equal(t, "capella", resp[0].Version) + require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp[0].Data.AttestedHeader.BodyRoot) + require.NotNil(t, resp) } func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCount(t *testing.T) { @@ -274,12 +274,12 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCount(t *tes s.GetLightClientUpdatesByRange(writer, request) require.Equal(t, http.StatusOK, writer.Code) - resp := &LightClientUpdatesByRangeResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 1, len(resp.Updates)) // Even with big count input, the response is still the max available period, which is 1 in test case. - require.Equal(t, "capella", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp.Updates[0].Data.AttestedHeader.BodyRoot) - require.NotNil(t, resp.Updates) + var resp []LightClientUpdateWithVersion + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.Equal(t, 1, len(resp)) // Even with big count input, the response is still the max available period, which is 1 in test case. + require.Equal(t, "capella", resp[0].Version) + require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp[0].Data.AttestedHeader.BodyRoot) + require.NotNil(t, resp) } func TestLightClientHandler_GetLightClientUpdatesByRange_TooEarlyPeriod(t *testing.T) { @@ -377,12 +377,12 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooEarlyPeriod(t *testi s.GetLightClientUpdatesByRange(writer, request) require.Equal(t, http.StatusOK, writer.Code) - resp := &LightClientUpdatesByRangeResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "capella", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp.Updates[0].Data.AttestedHeader.BodyRoot) - require.NotNil(t, resp.Updates) + var resp []LightClientUpdateWithVersion + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.Equal(t, 1, len(resp)) + require.Equal(t, "capella", resp[0].Version) + require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp[0].Data.AttestedHeader.BodyRoot) + require.NotNil(t, resp) } func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigCount(t *testing.T) { @@ -480,12 +480,12 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigCount(t *testing. s.GetLightClientUpdatesByRange(writer, request) require.Equal(t, http.StatusOK, writer.Code) - resp := &LightClientUpdatesByRangeResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "capella", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp.Updates[0].Data.AttestedHeader.BodyRoot) - require.NotNil(t, resp.Updates) + var resp []LightClientUpdateWithVersion + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.Equal(t, 1, len(resp)) + require.Equal(t, "capella", resp[0].Version) + require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), resp[0].Data.AttestedHeader.BodyRoot) + require.NotNil(t, resp) } func TestLightClientHandler_GetLightClientUpdatesByRange_BeforeAltair(t *testing.T) { @@ -583,9 +583,6 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_BeforeAltair(t *testing s.GetLightClientUpdatesByRange(writer, request) require.Equal(t, http.StatusNotFound, writer.Code) - resp := &LightClientUpdatesByRangeResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, 0, len(resp.Updates)) } func TestLightClientHandler_GetLightClientFinalityUpdate(t *testing.T) { diff --git a/beacon-chain/rpc/eth/light-client/helpers.go b/beacon-chain/rpc/eth/light-client/helpers.go index 277fcafcd6..98e2106547 100644 --- a/beacon-chain/rpc/eth/light-client/helpers.go +++ b/beacon-chain/rpc/eth/light-client/helpers.go @@ -296,11 +296,16 @@ func newLightClientUpdateToJSON(input *v2.LightClientUpdate) *LightClientUpdate nextSyncCommittee = shared.SyncCommitteeFromConsensus(migration.V2SyncCommitteeToV1Alpha1(input.NextSyncCommittee)) } + var finalizedHeader *shared.BeaconBlockHeader + if input.FinalizedHeader != nil { + finalizedHeader = shared.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(input.FinalizedHeader)) + } + return &LightClientUpdate{ AttestedHeader: shared.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(input.AttestedHeader)), NextSyncCommittee: nextSyncCommittee, NextSyncCommitteeBranch: branchToJSON(input.NextSyncCommitteeBranch), - FinalizedHeader: shared.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(input.FinalizedHeader)), + FinalizedHeader: finalizedHeader, FinalityBranch: branchToJSON(input.FinalityBranch), SyncAggregate: syncAggregateToJSON(input.SyncAggregate), SignatureSlot: strconv.FormatUint(uint64(input.SignatureSlot), 10), diff --git a/beacon-chain/rpc/eth/light-client/structs.go b/beacon-chain/rpc/eth/light-client/structs.go index 662262ebdb..822545505f 100644 --- a/beacon-chain/rpc/eth/light-client/structs.go +++ b/beacon-chain/rpc/eth/light-client/structs.go @@ -17,11 +17,11 @@ type LightClientBootstrap struct { type LightClientUpdate struct { AttestedHeader *shared.BeaconBlockHeader `json:"attested_header"` - NextSyncCommittee *shared.SyncCommittee `json:"next_sync_committee"` - FinalizedHeader *shared.BeaconBlockHeader `json:"finalized_header"` + NextSyncCommittee *shared.SyncCommittee `json:"next_sync_committee,omitempty"` + FinalizedHeader *shared.BeaconBlockHeader `json:"finalized_header,omitempty"` SyncAggregate *shared.SyncAggregate `json:"sync_aggregate"` - NextSyncCommitteeBranch []string `json:"next_sync_committee_branch"` - FinalityBranch []string `json:"finality_branch"` + NextSyncCommitteeBranch []string `json:"next_sync_committee_branch,omitempty"` + FinalityBranch []string `json:"finality_branch,omitempty"` SignatureSlot string `json:"signature_slot"` } diff --git a/config/features/config.go b/config/features/config.go index 1eef337901..1ff43273f3 100644 --- a/config/features/config.go +++ b/config/features/config.go @@ -23,10 +23,11 @@ import ( "sync" "time" - "github.com/prysmaticlabs/prysm/v4/cmd" - "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" + + "github.com/prysmaticlabs/prysm/v4/cmd" + "github.com/prysmaticlabs/prysm/v4/config/params" ) var log = logrus.WithField("prefix", "flags") @@ -40,6 +41,7 @@ type Flags struct { EnableExperimentalState bool // EnableExperimentalState turns on the latest and greatest (but potentially unstable) changes to the beacon state. WriteSSZStateTransitions bool // WriteSSZStateTransitions to tmp directory. EnablePeerScorer bool // EnablePeerScorer enables experimental peer scoring in p2p. + EnableLightClient bool // EnableLightClient enables light client APIs. WriteWalletPasswordOnWebOnboarding bool // WriteWalletPasswordOnWebOnboarding writes the password to disk after Prysm web signup. EnableDoppelGanger bool // EnableDoppelGanger enables doppelganger protection on startup for the validator. EnableHistoricalSpaceRepresentation bool // EnableHistoricalSpaceRepresentation enables the saving of registry validators in separate buckets to save space @@ -237,6 +239,10 @@ func ConfigureBeaconChain(ctx *cli.Context) error { logEnabled(EnableEIP4881) cfg.EnableEIP4881 = true } + if ctx.IsSet(EnableLightClient.Name) { + logEnabled(EnableLightClient) + cfg.EnableLightClient = true + } cfg.AggregateIntervals = [3]time.Duration{aggregateFirstInterval.Value, aggregateSecondInterval.Value, aggregateThirdInterval.Value} Init(cfg) return nil diff --git a/config/features/flags.go b/config/features/flags.go index 09c3bdbf3c..0327a973cd 100644 --- a/config/features/flags.go +++ b/config/features/flags.go @@ -140,6 +140,10 @@ var ( Name: "enable-eip-4881", Usage: "Enables the deposit tree specified in EIP-4881.", } + EnableLightClient = &cli.BoolFlag{ + Name: "enable-lightclient", + Usage: "Enables the light client support in the beacon node", + } disableResourceManager = &cli.BoolFlag{ Name: "disable-resource-manager", Usage: "Disables running the libp2p resource manager.", @@ -204,6 +208,7 @@ var BeaconChainFlags = append(deprecatedBeaconFlags, append(deprecatedFlags, []c EnableEIP4881, disableResourceManager, DisableRegistrationCache, + EnableLightClient, }...)...) // E2EBeaconChainFlags contains a list of the beacon chain feature flags to be tested in E2E.