diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index 33d653b6028e..8eee4ca9b0bb 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -59,6 +59,7 @@ go_library( "//beacon-chain/forkchoice:go_default_library", "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", "//beacon-chain/forkchoice/types:go_default_library", + "//beacon-chain/light-client:go_default_library", "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/blstoexec:go_default_library", "//beacon-chain/operations/slashings:go_default_library", diff --git a/beacon-chain/blockchain/options.go b/beacon-chain/blockchain/options.go index caf746cc74d0..a341f6030bd5 100644 --- a/beacon-chain/blockchain/options.go +++ b/beacon-chain/blockchain/options.go @@ -8,6 +8,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filesystem" "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice" + light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/blstoexec" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" @@ -213,3 +214,10 @@ func WithSyncChecker(checker Checker) Option { return nil } } + +func WithLightClientStore(lcs *light_client.Store) Option { + return func(s *Service) error { + s.lcStore = lcs + return nil + } +} diff --git a/beacon-chain/blockchain/process_block_helpers.go b/beacon-chain/blockchain/process_block_helpers.go index e4a37ff4b221..008d2be155b8 100644 --- a/beacon-chain/blockchain/process_block_helpers.go +++ b/beacon-chain/blockchain/process_block_helpers.go @@ -7,12 +7,11 @@ import ( "strings" "time" - lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" - "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed" statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state" + lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition" doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree" forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types" @@ -262,15 +261,47 @@ func (s *Service) processLightClientFinalityUpdate( attestedBlock, finalizedBlock, ) - if err != nil { return errors.Wrap(err, "could not create light client finality update") } + log.Info("LC: created new finality update post-block") + log.Infof("%v", update) + + maxActiveParticipants := update.SyncAggregate().SyncCommitteeBits.Len() + numActiveParticipants := update.SyncAggregate().SyncCommitteeBits.Count() + hasSupermajority := numActiveParticipants*3 >= maxActiveParticipants*2 + + last := s.lcStore.LastLCFinalityUpdate + if last != nil { + // The finalized_header.beacon.slot is greater than that of all previously forwarded finality_updates, + // or it matches the highest previously forwarded slot and also has a sync_aggregate indicating supermajority (> 2/3) + // sync committee participation while the previously forwarded finality_update for that slot did not indicate supermajority + slot := last.FinalizedHeader().Beacon().Slot + lastMaxActiveParticipants := last.SyncAggregate().SyncCommitteeBits.Len() + lastNumActiveParticipants := last.SyncAggregate().SyncCommitteeBits.Count() + lastHasSupermajority := lastNumActiveParticipants*3 >= lastMaxActiveParticipants*2 + + if update.FinalizedHeader().Beacon().Slot < slot { + return nil + } + if update.FinalizedHeader().Beacon().Slot == slot && (lastHasSupermajority || !hasSupermajority) { + return nil + } + } + + log.Info("LC: storing new finality update post-block") + log.Infof("%v", update) + s.lcStore.LastLCFinalityUpdate = update + + if err = s.cfg.P2p.BroadcastLightClientFinalityUpdate(ctx, update); err != nil { + return errors.Wrap(err, "could not broadcast light client finality update") + } s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ Type: statefeed.LightClientFinalityUpdate, Data: update, }) + return nil } @@ -294,7 +325,6 @@ func (s *Service) processLightClientOptimisticUpdate(ctx context.Context, signed attestedState, attestedBlock, ) - if err != nil { if strings.Contains(err.Error(), lightclient.ErrNotEnoughSyncCommitteeBits) { log.WithError(err).Debug("Skipping processing light client optimistic update") @@ -303,6 +333,15 @@ func (s *Service) processLightClientOptimisticUpdate(ctx context.Context, signed return errors.Wrap(err, "could not create light client optimistic update") } + last := s.lcStore.LastLCOptimisticUpdate + if last != nil { + // The attested_header.beacon.slot is greater than that of all previously forwarded optimistic_updates + if update.AttestedHeader().Beacon().Slot <= last.AttestedHeader().Beacon().Slot { + return nil + } + log.Debug("Skipping processing light client optimistic update because attested header's slot is not newer") + } + s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ Type: statefeed.LightClientOptimisticUpdate, Data: update, diff --git a/beacon-chain/blockchain/service.go b/beacon-chain/blockchain/service.go index dbd7685714bd..f6bf6244db06 100644 --- a/beacon-chain/blockchain/service.go +++ b/beacon-chain/blockchain/service.go @@ -24,6 +24,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" f "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice" forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types" + light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/blstoexec" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" @@ -65,6 +66,7 @@ type Service struct { blobNotifiers *blobNotifierMap blockBeingSynced *currentlySyncingBlock blobStorage *filesystem.BlobStorage + lcStore *light_client.Store } // config options for the service. diff --git a/beacon-chain/light-client/BUILD.bazel b/beacon-chain/light-client/BUILD.bazel new file mode 100644 index 000000000000..4c36f9363a8d --- /dev/null +++ b/beacon-chain/light-client/BUILD.bazel @@ -0,0 +1,9 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["store.go"], + importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client", + visibility = ["//visibility:public"], + deps = ["//consensus-types/interfaces:go_default_library"], +) diff --git a/beacon-chain/light-client/store.go b/beacon-chain/light-client/store.go new file mode 100644 index 000000000000..bd79e568a9d2 --- /dev/null +++ b/beacon-chain/light-client/store.go @@ -0,0 +1,10 @@ +package light_client + +import ( + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" +) + +type Store struct { + LastLCFinalityUpdate interfaces.LightClientFinalityUpdate + LastLCOptimisticUpdate interfaces.LightClientOptimisticUpdate +} diff --git a/beacon-chain/node/BUILD.bazel b/beacon-chain/node/BUILD.bazel index ab3eb9cd5654..61d82f3439a6 100644 --- a/beacon-chain/node/BUILD.bazel +++ b/beacon-chain/node/BUILD.bazel @@ -29,6 +29,7 @@ go_library( "//beacon-chain/execution:go_default_library", "//beacon-chain/forkchoice:go_default_library", "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", + "//beacon-chain/light-client:go_default_library", "//beacon-chain/monitor:go_default_library", "//beacon-chain/node/registration:go_default_library", "//beacon-chain/operations/attestations:go_default_library", diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 65c447f086fd..05b578b73354 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -33,6 +33,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice" doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree" + light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/monitor" "github.com/prysmaticlabs/prysm/v5/beacon-chain/node/registration" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations" @@ -121,6 +122,7 @@ type BeaconNode struct { BlobStorageOptions []filesystem.BlobStorageOption verifyInitWaiter *verification.InitializerWaiter syncChecker *initialsync.SyncChecker + lcStore *light_client.Store } // New creates a new node instance, sets up configuration options, and registers @@ -158,6 +160,7 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco serviceFlagOpts: &serviceFlagOpts{}, initialSyncComplete: make(chan struct{}), syncChecker: &initialsync.SyncChecker{}, + lcStore: &light_client.Store{}, } for _, opt := range opts { @@ -752,6 +755,7 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st blockchain.WithTrackedValidatorsCache(b.trackedValidatorsCache), blockchain.WithPayloadIDCache(b.payloadIDCache), blockchain.WithSyncChecker(b.syncChecker), + blockchain.WithLightClientStore(b.lcStore), ) blockchainService, err := blockchain.NewService(b.ctx, opts...) @@ -836,6 +840,7 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}, bFil regularsync.WithBlobStorage(b.BlobStorage), regularsync.WithVerifierWaiter(b.verifyInitWaiter), regularsync.WithAvailableBlocker(bFillStore), + regularsync.WithLightClientStore(b.lcStore), ) return b.services.RegisterService(rs) } @@ -982,6 +987,7 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error { BlobStorage: b.BlobStorage, TrackedValidatorsCache: b.trackedValidatorsCache, PayloadIDCache: b.payloadIDCache, + LCStore: b.lcStore, }) return b.services.RegisterService(rpcService) diff --git a/beacon-chain/p2p/BUILD.bazel b/beacon-chain/p2p/BUILD.bazel index edf1cf95bc18..17b61ae65ae1 100644 --- a/beacon-chain/p2p/BUILD.bazel +++ b/beacon-chain/p2p/BUILD.bazel @@ -56,6 +56,7 @@ go_library( "//cmd/beacon-chain/flags:go_default_library", "//config/features:go_default_library", "//config/params:go_default_library", + "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", "//consensus-types/wrapper:go_default_library", "//container/leaky-bucket:go_default_library", diff --git a/beacon-chain/p2p/broadcaster.go b/beacon-chain/p2p/broadcaster.go index 2cb8459fbfbc..1014a8a576bf 100644 --- a/beacon-chain/p2p/broadcaster.go +++ b/beacon-chain/p2p/broadcaster.go @@ -12,9 +12,11 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/crypto/hash" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" + "github.com/prysmaticlabs/prysm/v5/network/forks" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/sirupsen/logrus" @@ -268,6 +270,63 @@ func (s *Service) internalBroadcastBlob(ctx context.Context, subnet uint64, blob } } +func (s *Service) BroadcastLightClientOptimisticUpdate(ctx context.Context, update interfaces.LightClientOptimisticUpdate) error { + ctx, span := trace.StartSpan(ctx, "p2p.BroadcastLightClientOptimisticUpdate") + defer span.End() + + log.Info("LC: broadcasting optimistic update") + + forkDigest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(update.AttestedHeader().Beacon().Slot), s.genesisValidatorsRoot) + if err != nil { + err := errors.Wrap(err, "could not retrieve fork digest") + tracing.AnnotateError(span, err) + log.WithError(err).Error("forks.ForkDigestFromEpoch") + return err + } + topic, ok := GossipTypeMapping[reflect.TypeOf(update.Proto())] + if !ok { + tracing.AnnotateError(span, ErrMessageNotMapped) + log.Error("GossipTypeMapping") + return ErrMessageNotMapped + } + if err = s.broadcastObject(ctx, update, fmt.Sprintf(topic, forkDigest)); err != nil { + log.WithError(err).Error("s.broadcastObject") + tracing.AnnotateError(span, err) + } + + log.Info("LC: successfully broadcast optimistic update") + + return nil +} + +func (s *Service) BroadcastLightClientFinalityUpdate(ctx context.Context, update interfaces.LightClientFinalityUpdate) error { + ctx, span := trace.StartSpan(ctx, "p2p.BroadcastLightClientFinalityUpdate") + defer span.End() + + log.Info("LC: broadcasting finality update") + + forkDigest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(update.AttestedHeader().Beacon().Slot), s.genesisValidatorsRoot) + if err != nil { + err := errors.Wrap(err, "could not retrieve fork digest") + tracing.AnnotateError(span, err) + log.WithError(err).Error("forks.ForkDigestFromEpoch") + return err + } + topic, ok := GossipTypeMapping[reflect.TypeOf(update.Proto())] + if !ok { + tracing.AnnotateError(span, ErrMessageNotMapped) + return ErrMessageNotMapped + } + if err = s.broadcastObject(ctx, update, fmt.Sprintf(topic, forkDigest)); err != nil { + log.WithError(err).Error("s.broadcastObject") + tracing.AnnotateError(span, err) + } + + log.Info("LC: successfully broadcast finality update") + + return nil +} + // method to broadcast messages to other peers in our gossip mesh. func (s *Service) broadcastObject(ctx context.Context, obj ssz.Marshaler, topic string) error { ctx, span := trace.StartSpan(ctx, "p2p.broadcastObject") diff --git a/beacon-chain/p2p/gossip_scoring_params.go b/beacon-chain/p2p/gossip_scoring_params.go index afe667283435..2e522a31c5b3 100644 --- a/beacon-chain/p2p/gossip_scoring_params.go +++ b/beacon-chain/p2p/gossip_scoring_params.go @@ -44,6 +44,12 @@ const ( // blsToExecutionChangeWeight specifies the scoring weight that we apply to // our bls to execution topic. blsToExecutionChangeWeight = 0.05 + // lightClientFinalityUpdateWeight specifies the scoring weight that we apply to + // our light client finality update topic. + lightClientFinalityUpdateWeight = 0.05 + // lightClientOptimisticUpdateWeight specifies the scoring weight that we apply to + // our light client optimistic update topic. + lightClientOptimisticUpdateWeight = 0.05 // maxInMeshScore describes the max score a peer can attain from being in the mesh. maxInMeshScore = 10 @@ -124,6 +130,10 @@ func (s *Service) topicScoreParams(topic string) (*pubsub.TopicScoreParams, erro case strings.Contains(topic, GossipBlobSidecarMessage): // TODO(Deneb): Using the default block scoring. But this should be updated. return defaultBlockTopicParams(), nil + case strings.Contains(topic, GossipLightClientFinalityUpdateMessage): + return defaultLightClientFinalityUpdateTopicParams(), nil + case strings.Contains(topic, GossipLightClientOptimisticUpdateMessage): + return defaultLightClientOptimisticUpdateTopicParams(), nil default: return nil, errors.Errorf("unrecognized topic provided for parameter registration: %s", topic) } @@ -503,6 +513,50 @@ func defaultBlsToExecutionChangeTopicParams() *pubsub.TopicScoreParams { } } +func defaultLightClientFinalityUpdateTopicParams() *pubsub.TopicScoreParams { + return &pubsub.TopicScoreParams{ + TopicWeight: lightClientFinalityUpdateWeight, + TimeInMeshWeight: maxInMeshScore / inMeshCap(), + TimeInMeshQuantum: inMeshTime(), + TimeInMeshCap: inMeshCap(), + FirstMessageDeliveriesWeight: 2, + FirstMessageDeliveriesDecay: scoreDecay(oneHundredEpochs), + FirstMessageDeliveriesCap: 5, + MeshMessageDeliveriesWeight: 0, + MeshMessageDeliveriesDecay: 0, + MeshMessageDeliveriesCap: 0, + MeshMessageDeliveriesThreshold: 0, + MeshMessageDeliveriesWindow: 0, + MeshMessageDeliveriesActivation: 0, + MeshFailurePenaltyWeight: 0, + MeshFailurePenaltyDecay: 0, + InvalidMessageDeliveriesWeight: -2000, + InvalidMessageDeliveriesDecay: scoreDecay(invalidDecayPeriod), + } +} + +func defaultLightClientOptimisticUpdateTopicParams() *pubsub.TopicScoreParams { + return &pubsub.TopicScoreParams{ + TopicWeight: lightClientOptimisticUpdateWeight, + TimeInMeshWeight: maxInMeshScore / inMeshCap(), + TimeInMeshQuantum: inMeshTime(), + TimeInMeshCap: inMeshCap(), + FirstMessageDeliveriesWeight: 2, + FirstMessageDeliveriesDecay: scoreDecay(oneHundredEpochs), + FirstMessageDeliveriesCap: 5, + MeshMessageDeliveriesWeight: 0, + MeshMessageDeliveriesDecay: 0, + MeshMessageDeliveriesCap: 0, + MeshMessageDeliveriesThreshold: 0, + MeshMessageDeliveriesWindow: 0, + MeshMessageDeliveriesActivation: 0, + MeshFailurePenaltyWeight: 0, + MeshFailurePenaltyDecay: 0, + InvalidMessageDeliveriesWeight: -2000, + InvalidMessageDeliveriesDecay: scoreDecay(invalidDecayPeriod), + } +} + func oneSlotDuration() time.Duration { return time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second } diff --git a/beacon-chain/p2p/gossip_topic_mappings.go b/beacon-chain/p2p/gossip_topic_mappings.go index 36567d36e71d..9ca78f652b3d 100644 --- a/beacon-chain/p2p/gossip_topic_mappings.go +++ b/beacon-chain/p2p/gossip_topic_mappings.go @@ -22,6 +22,8 @@ var gossipTopicMappings = map[string]func() proto.Message{ SyncCommitteeSubnetTopicFormat: func() proto.Message { return ðpb.SyncCommitteeMessage{} }, BlsToExecutionChangeSubnetTopicFormat: func() proto.Message { return ðpb.SignedBLSToExecutionChange{} }, BlobSubnetTopicFormat: func() proto.Message { return ðpb.BlobSidecar{} }, + LightClientFinalityUpdateTopicFormat: func() proto.Message { return ðpb.LightClientFinalityUpdateAltair{} }, + LightClientOptimisticUpdateTopicFormat: func() proto.Message { return ðpb.LightClientOptimisticUpdateAltair{} }, } // GossipTopicMappings is a function to return the assigned data type @@ -63,6 +65,25 @@ func GossipTopicMappings(topic string, epoch primitives.Epoch) proto.Message { return ðpb.SignedAggregateAttestationAndProofElectra{} } return gossipMessage(topic) + case LightClientFinalityUpdateTopicFormat: + if epoch >= params.BeaconConfig().ElectraForkEpoch { + return ðpb.LightClientFinalityUpdateElectra{} + } + if epoch >= params.BeaconConfig().DenebForkEpoch { + return ðpb.LightClientFinalityUpdateDeneb{} + } + if epoch >= params.BeaconConfig().CapellaForkEpoch { + return ðpb.LightClientFinalityUpdateCapella{} + } + return gossipMessage(topic) + case LightClientOptimisticUpdateTopicFormat: + if epoch >= params.BeaconConfig().DenebForkEpoch { + return ðpb.LightClientOptimisticUpdateDeneb{} + } + if epoch >= params.BeaconConfig().CapellaForkEpoch { + return ðpb.LightClientOptimisticUpdateCapella{} + } + return gossipMessage(topic) default: return gossipMessage(topic) } @@ -103,15 +124,20 @@ func init() { // Specially handle Capella objects. GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockCapella{})] = BlockSubnetTopicFormat + GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateCapella{})] = LightClientOptimisticUpdateTopicFormat + GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateCapella{})] = LightClientFinalityUpdateTopicFormat // Specially handle Deneb objects. GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockDeneb{})] = BlockSubnetTopicFormat + GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateDeneb{})] = LightClientOptimisticUpdateTopicFormat + GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateDeneb{})] = LightClientFinalityUpdateTopicFormat // Specially handle Electra objects. GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockElectra{})] = BlockSubnetTopicFormat GossipTypeMapping[reflect.TypeOf(ðpb.AttestationElectra{})] = AttestationSubnetTopicFormat GossipTypeMapping[reflect.TypeOf(ðpb.AttesterSlashingElectra{})] = AttesterSlashingSubnetTopicFormat GossipTypeMapping[reflect.TypeOf(ðpb.SignedAggregateAttestationAndProofElectra{})] = AggregateAndProofSubnetTopicFormat + GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateElectra{})] = LightClientFinalityUpdateTopicFormat // Specially handle Fulu objects. GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockFulu{})] = BlockSubnetTopicFormat diff --git a/beacon-chain/p2p/interfaces.go b/beacon-chain/p2p/interfaces.go index 2f9a1d0ce2ad..a5427c9430b6 100644 --- a/beacon-chain/p2p/interfaces.go +++ b/beacon-chain/p2p/interfaces.go @@ -12,6 +12,7 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/encoder" "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/peers" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/metadata" "google.golang.org/protobuf/proto" @@ -36,6 +37,8 @@ type Broadcaster interface { BroadcastAttestation(ctx context.Context, subnet uint64, att ethpb.Att) error BroadcastSyncCommitteeMessage(ctx context.Context, subnet uint64, sMsg *ethpb.SyncCommitteeMessage) error BroadcastBlob(ctx context.Context, subnet uint64, blob *ethpb.BlobSidecar) error + BroadcastLightClientOptimisticUpdate(ctx context.Context, update interfaces.LightClientOptimisticUpdate) error + BroadcastLightClientFinalityUpdate(ctx context.Context, update interfaces.LightClientFinalityUpdate) error } // SetStreamHandler configures p2p to handle streams of a certain topic ID. diff --git a/beacon-chain/p2p/rpc_topic_mappings.go b/beacon-chain/p2p/rpc_topic_mappings.go index a50555e82edc..2f1ffe2c5ade 100644 --- a/beacon-chain/p2p/rpc_topic_mappings.go +++ b/beacon-chain/p2p/rpc_topic_mappings.go @@ -43,6 +43,18 @@ const BlobSidecarsByRangeName = "/blob_sidecars_by_range" // BlobSidecarsByRootName is the name for the BlobSidecarsByRoot v1 message topic. const BlobSidecarsByRootName = "/blob_sidecars_by_root" +// LightClientBootstrapName is the name for the LightClientBootstrap message topic, +const LightClientBootstrapName = "/light_client_bootstrap" + +// LightClientUpdatesByRangeName is the name for the LightClientUpdatesByRange topic. +const LightClientUpdatesByRangeName = "/light_client_updates_by_range" + +// LightClientFinalityUpdateName is the name for the LightClientFinalityUpdate topic. +const LightClientFinalityUpdateName = "/light_client_finality_update" + +// LightClientOptimisticUpdateName is the name for the LightClientOptimisticUpdate topic. +const LightClientOptimisticUpdateName = "/light_client_optimistic_update" + const ( // V1 RPC Topics // RPCStatusTopicV1 defines the v1 topic for the status rpc method. @@ -66,6 +78,18 @@ const ( // /eth2/beacon_chain/req/blob_sidecars_by_root/1/ RPCBlobSidecarsByRootTopicV1 = protocolPrefix + BlobSidecarsByRootName + SchemaVersionV1 + // RPCLightClientBootstrapTopicV1 is a topic for requesting a light client bootstrap. + RPCLightClientBootstrapTopicV1 = protocolPrefix + LightClientBootstrapName + SchemaVersionV1 + + // RPCLightClientUpdatesByRangeTopicV1 is a topic for requesting light client updates by range. + RPCLightClientUpdatesByRangeTopicV1 = protocolPrefix + LightClientUpdatesByRangeName + SchemaVersionV1 + + // RPCLightClientFinalityUpdateTopicV1 is a topic for requesting a light client finality update. + RPCLightClientFinalityUpdateTopicV1 = protocolPrefix + LightClientFinalityUpdateName + SchemaVersionV1 + + // RPCLightClientOptimisticUpdateTopicV1 is a topic for requesting a light client Optimistic update. + RPCLightClientOptimisticUpdateTopicV1 = protocolPrefix + LightClientOptimisticUpdateName + SchemaVersionV1 + // V2 RPC Topics // RPCBlocksByRangeTopicV2 defines v2 the topic for the blocks by range rpc method. RPCBlocksByRangeTopicV2 = protocolPrefix + BeaconBlocksByRangeMessageName + SchemaVersionV2 @@ -104,6 +128,11 @@ var RPCTopicMappings = map[string]interface{}{ RPCBlobSidecarsByRangeTopicV1: new(pb.BlobSidecarsByRangeRequest), // BlobSidecarsByRoot v1 Message RPCBlobSidecarsByRootTopicV1: new(p2ptypes.BlobSidecarsByRootReq), + // Light client + RPCLightClientBootstrapTopicV1: new(p2ptypes.LightClientBootstrapReq), + RPCLightClientUpdatesByRangeTopicV1: new(pb.LightClientUpdatesByRangeReq), + RPCLightClientFinalityUpdateTopicV1: new(interface{}), + RPCLightClientOptimisticUpdateTopicV1: new(interface{}), } // Maps all registered protocol prefixes. @@ -114,14 +143,18 @@ var protocolMapping = map[string]bool{ // Maps all the protocol message names for the different rpc // topics. var messageMapping = map[string]bool{ - StatusMessageName: true, - GoodbyeMessageName: true, - BeaconBlocksByRangeMessageName: true, - BeaconBlocksByRootsMessageName: true, - PingMessageName: true, - MetadataMessageName: true, - BlobSidecarsByRangeName: true, - BlobSidecarsByRootName: true, + StatusMessageName: true, + GoodbyeMessageName: true, + BeaconBlocksByRangeMessageName: true, + BeaconBlocksByRootsMessageName: true, + PingMessageName: true, + MetadataMessageName: true, + BlobSidecarsByRangeName: true, + BlobSidecarsByRootName: true, + LightClientBootstrapName: true, + LightClientUpdatesByRangeName: true, + LightClientFinalityUpdateName: true, + LightClientOptimisticUpdateName: true, } // Maps all the RPC messages which are to updated in altair. diff --git a/beacon-chain/p2p/topics.go b/beacon-chain/p2p/topics.go index 3187e36a5cfe..966c5c4183be 100644 --- a/beacon-chain/p2p/topics.go +++ b/beacon-chain/p2p/topics.go @@ -30,6 +30,10 @@ const ( GossipBlsToExecutionChangeMessage = "bls_to_execution_change" // GossipBlobSidecarMessage is the name for the blob sidecar message type. GossipBlobSidecarMessage = "blob_sidecar" + // GossipLightClientFinalityUpdateMessage is the name for the light client finality update message type. + GossipLightClientFinalityUpdateMessage = "light_client_finality_update" + // GossipLightClientOptimisticUpdateMessage is the name for the light client optimistic update message type. + GossipLightClientOptimisticUpdateMessage = "light_client_optimistic_update" // Topic Formats // // AttestationSubnetTopicFormat is the topic format for the attestation subnet. @@ -52,4 +56,8 @@ const ( BlsToExecutionChangeSubnetTopicFormat = GossipProtocolAndDigest + GossipBlsToExecutionChangeMessage // BlobSubnetTopicFormat is the topic format for the blob subnet. BlobSubnetTopicFormat = GossipProtocolAndDigest + GossipBlobSidecarMessage + "_%d" + // LightClientFinalityUpdateTopicFormat is the topic format for the light client finality update subnet. + LightClientFinalityUpdateTopicFormat = GossipProtocolAndDigest + GossipLightClientFinalityUpdateMessage + // LightClientOptimisticUpdateTopicFormat is the topic format for the light client optimistic update subnet. + LightClientOptimisticUpdateTopicFormat = GossipProtocolAndDigest + GossipLightClientOptimisticUpdateMessage ) diff --git a/beacon-chain/p2p/types/BUILD.bazel b/beacon-chain/p2p/types/BUILD.bazel index a66fcefd9e09..91122456cfb9 100644 --- a/beacon-chain/p2p/types/BUILD.bazel +++ b/beacon-chain/p2p/types/BUILD.bazel @@ -17,9 +17,11 @@ go_library( "//validator/client:__pkg__", ], deps = [ + "//config/fieldparams:go_default_library", "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/light-client:go_default_library", "//consensus-types/primitives:go_default_library", "//consensus-types/wrapper:go_default_library", "//encoding/bytesutil:go_default_library", diff --git a/beacon-chain/p2p/types/object_mapping.go b/beacon-chain/p2p/types/object_mapping.go index 4dcfb22d396c..ce7cbfdd4aac 100644 --- a/beacon-chain/p2p/types/object_mapping.go +++ b/beacon-chain/p2p/types/object_mapping.go @@ -1,9 +1,12 @@ package types import ( + 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/interfaces" + light_client "github.com/prysmaticlabs/prysm/v5/consensus-types/light-client" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/consensus-types/wrapper" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" @@ -33,6 +36,9 @@ var ( // AggregateAttestationMap maps the fork-version to the underlying data type for that // particular fork period. AggregateAttestationMap map[[4]byte]func() (ethpb.SignedAggregateAttAndProof, error) + + LightClientOptimisticUpdateMap map[[4]byte]func() (interfaces.LightClientOptimisticUpdate, error) + LightClientFinalityUpdateMap map[[4]byte]func() (interfaces.LightClientFinalityUpdate, error) ) // InitializeDataMaps initializes all the relevant object maps. This function is called to @@ -151,4 +157,124 @@ func InitializeDataMaps() { return ðpb.SignedAggregateAttestationAndProofElectra{}, nil }, } + + slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch + altairSlot := primitives.Slot(params.BeaconConfig().AltairForkEpoch) * slotsPerEpoch + bellatrixSlot := primitives.Slot(params.BeaconConfig().BellatrixForkEpoch) * slotsPerEpoch + capellaSlot := primitives.Slot(params.BeaconConfig().CapellaForkEpoch) * slotsPerEpoch + denebSlot := primitives.Slot(params.BeaconConfig().DenebForkEpoch) * slotsPerEpoch + electraSlot := primitives.Slot(params.BeaconConfig().ElectraForkEpoch) * slotsPerEpoch + + LightClientOptimisticUpdateMap = map[[4]byte]func() (interfaces.LightClientOptimisticUpdate, error){ + bytesutil.ToBytes4(params.BeaconConfig().AltairForkVersion): func() (interfaces.LightClientOptimisticUpdate, error) { + m := ðpb.LightClientOptimisticUpdateAltair{ + AttestedHeader: ðpb.LightClientHeaderAltair{Beacon: ðpb.BeaconBlockHeader{Slot: altairSlot}}, + } + return light_client.NewWrappedOptimisticUpdateAltair(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().BellatrixForkVersion): func() (interfaces.LightClientOptimisticUpdate, error) { + m := ðpb.LightClientOptimisticUpdateAltair{ + AttestedHeader: ðpb.LightClientHeaderAltair{Beacon: ðpb.BeaconBlockHeader{Slot: bellatrixSlot}}, + } + return light_client.NewWrappedOptimisticUpdateAltair(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().CapellaForkVersion): func() (interfaces.LightClientOptimisticUpdate, error) { + m := ðpb.LightClientOptimisticUpdateCapella{ + AttestedHeader: ðpb.LightClientHeaderCapella{Beacon: ðpb.BeaconBlockHeader{Slot: capellaSlot}}, + } + return light_client.NewWrappedOptimisticUpdateCapella(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().DenebForkVersion): func() (interfaces.LightClientOptimisticUpdate, error) { + m := ðpb.LightClientOptimisticUpdateDeneb{ + AttestedHeader: ðpb.LightClientHeaderDeneb{Beacon: ðpb.BeaconBlockHeader{Slot: denebSlot}}, + } + return light_client.NewWrappedOptimisticUpdateDeneb(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().ElectraForkVersion): func() (interfaces.LightClientOptimisticUpdate, error) { + m := ðpb.LightClientOptimisticUpdateDeneb{ + AttestedHeader: ðpb.LightClientHeaderDeneb{Beacon: ðpb.BeaconBlockHeader{Slot: electraSlot}}, + } + return light_client.NewWrappedOptimisticUpdateDeneb(m) + }, + } + + emptyExecutionBranch := make([][]byte, fieldparams.ExecutionBranchDepth) + emptyFinalityBranch := make([][]byte, fieldparams.FinalityBranchDepth) + emptyFinalityBranchElectra := make([][]byte, fieldparams.FinalityBranchDepthElectra) + for i := 0; i < len(emptyExecutionBranch); i++ { + emptyExecutionBranch[i] = make([]byte, 32) + } + for i := 0; i < len(emptyFinalityBranch); i++ { + emptyFinalityBranch[i] = make([]byte, 32) + } + for i := 0; i < len(emptyFinalityBranchElectra); i++ { + emptyFinalityBranchElectra[i] = make([]byte, 32) + } + + LightClientFinalityUpdateMap = map[[4]byte]func() (interfaces.LightClientFinalityUpdate, error){ + bytesutil.ToBytes4(params.BeaconConfig().AltairForkVersion): func() (interfaces.LightClientFinalityUpdate, error) { + m := ðpb.LightClientFinalityUpdateAltair{ + AttestedHeader: ðpb.LightClientHeaderAltair{Beacon: ðpb.BeaconBlockHeader{Slot: altairSlot}}, + FinalizedHeader: ðpb.LightClientHeaderAltair{Beacon: ðpb.BeaconBlockHeader{Slot: altairSlot}}, + FinalityBranch: emptyFinalityBranch, + } + return light_client.NewWrappedFinalityUpdateAltair(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().BellatrixForkVersion): func() (interfaces.LightClientFinalityUpdate, error) { + m := ðpb.LightClientFinalityUpdateAltair{ + AttestedHeader: ðpb.LightClientHeaderAltair{Beacon: ðpb.BeaconBlockHeader{Slot: bellatrixSlot}}, + FinalizedHeader: ðpb.LightClientHeaderAltair{Beacon: ðpb.BeaconBlockHeader{Slot: bellatrixSlot}}, + FinalityBranch: emptyFinalityBranch, + } + return light_client.NewWrappedFinalityUpdateAltair(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().CapellaForkVersion): func() (interfaces.LightClientFinalityUpdate, error) { + m := ðpb.LightClientFinalityUpdateCapella{ + AttestedHeader: ðpb.LightClientHeaderCapella{ + Beacon: ðpb.BeaconBlockHeader{Slot: capellaSlot}, + Execution: &enginev1.ExecutionPayloadHeaderCapella{}, + ExecutionBranch: emptyExecutionBranch, + }, + FinalizedHeader: ðpb.LightClientHeaderCapella{ + Beacon: ðpb.BeaconBlockHeader{Slot: capellaSlot}, + Execution: &enginev1.ExecutionPayloadHeaderCapella{}, + ExecutionBranch: emptyExecutionBranch, + }, + FinalityBranch: emptyFinalityBranch, + } + return light_client.NewWrappedFinalityUpdateCapella(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().DenebForkVersion): func() (interfaces.LightClientFinalityUpdate, error) { + m := ðpb.LightClientFinalityUpdateDeneb{ + AttestedHeader: ðpb.LightClientHeaderDeneb{ + Beacon: ðpb.BeaconBlockHeader{Slot: denebSlot}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: emptyExecutionBranch, + }, + FinalizedHeader: ðpb.LightClientHeaderDeneb{ + Beacon: ðpb.BeaconBlockHeader{Slot: denebSlot}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: emptyExecutionBranch, + }, + FinalityBranch: emptyFinalityBranch, + } + return light_client.NewWrappedFinalityUpdateDeneb(m) + }, + bytesutil.ToBytes4(params.BeaconConfig().ElectraForkVersion): func() (interfaces.LightClientFinalityUpdate, error) { + m := ðpb.LightClientFinalityUpdateElectra{ + AttestedHeader: ðpb.LightClientHeaderDeneb{ + Beacon: ðpb.BeaconBlockHeader{Slot: electraSlot}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: emptyExecutionBranch, + }, + FinalizedHeader: ðpb.LightClientHeaderDeneb{ + Beacon: ðpb.BeaconBlockHeader{Slot: electraSlot}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: emptyExecutionBranch, + }, + FinalityBranch: emptyFinalityBranchElectra, + } + return light_client.NewWrappedFinalityUpdateElectra(m) + }, + } } diff --git a/beacon-chain/p2p/types/rpc_errors.go b/beacon-chain/p2p/types/rpc_errors.go index 46381876c118..2ca23d120e91 100644 --- a/beacon-chain/p2p/types/rpc_errors.go +++ b/beacon-chain/p2p/types/rpc_errors.go @@ -5,6 +5,7 @@ import "errors" var ( ErrWrongForkDigestVersion = errors.New("wrong fork digest version") ErrInvalidEpoch = errors.New("invalid epoch") + ErrInvalidRoot = errors.New("invalid root") ErrInvalidFinalizedRoot = errors.New("invalid finalized root") ErrInvalidSequenceNum = errors.New("invalid sequence number provided") ErrGeneric = errors.New("internal service error") diff --git a/beacon-chain/p2p/types/types.go b/beacon-chain/p2p/types/types.go index 2ccda62f326d..537050a71409 100644 --- a/beacon-chain/p2p/types/types.go +++ b/beacon-chain/p2p/types/types.go @@ -9,13 +9,15 @@ import ( "github.com/pkg/errors" ssz "github.com/prysmaticlabs/fastssz" + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ) -const rootLength = 32 - -const maxErrorLength = 256 +const ( + maxErrorLength = 256 + maxLightClientUpdates = 16 +) // SSZBytes is a bytes slice that satisfies the fast-ssz interface. type SSZBytes []byte @@ -34,7 +36,7 @@ func (b *SSZBytes) HashTreeRootWith(hh *ssz.Hasher) error { } // BeaconBlockByRootsReq specifies the block by roots request type. -type BeaconBlockByRootsReq [][rootLength]byte +type BeaconBlockByRootsReq [][fieldparams.RootLength]byte // MarshalSSZTo marshals the block by roots request with the provided byte slice. func (r *BeaconBlockByRootsReq) MarshalSSZTo(dst []byte) ([]byte, error) { @@ -59,25 +61,25 @@ func (r *BeaconBlockByRootsReq) MarshalSSZ() ([]byte, error) { // SizeSSZ returns the size of the serialized representation. func (r *BeaconBlockByRootsReq) SizeSSZ() int { - return len(*r) * rootLength + return len(*r) * fieldparams.RootLength } // UnmarshalSSZ unmarshals the provided bytes buffer into the // block by roots request object. func (r *BeaconBlockByRootsReq) UnmarshalSSZ(buf []byte) error { bufLen := len(buf) - maxLength := int(params.BeaconConfig().MaxRequestBlocks * rootLength) + maxLength := int(params.BeaconConfig().MaxRequestBlocks * fieldparams.RootLength) if bufLen > maxLength { return errors.Errorf("expected buffer with length of up to %d but received length %d", maxLength, bufLen) } - if bufLen%rootLength != 0 { + if bufLen%fieldparams.RootLength != 0 { return ssz.ErrIncorrectByteSize } - numOfRoots := bufLen / rootLength - roots := make([][rootLength]byte, 0, numOfRoots) + numOfRoots := bufLen / fieldparams.RootLength + roots := make([][fieldparams.RootLength]byte, 0, numOfRoots) for i := 0; i < numOfRoots; i++ { - var rt [rootLength]byte - copy(rt[:], buf[i*rootLength:(i+1)*rootLength]) + var rt [fieldparams.RootLength]byte + copy(rt[:], buf[i*fieldparams.RootLength:(i+1)*fieldparams.RootLength]) roots = append(roots, rt) } *r = roots @@ -211,3 +213,36 @@ func init() { sizer := ð.BlobIdentifier{} blobIdSize = sizer.SizeSSZ() } + +// LightClientBootstrapReq specifies the light client bootstrap request type. +type LightClientBootstrapReq [fieldparams.RootLength]byte + +// MarshalSSZTo marshals the block by roots request with the provided byte slice. +func (r *LightClientBootstrapReq) MarshalSSZTo(dst []byte) ([]byte, error) { + marshalledObj, err := r.MarshalSSZ() + if err != nil { + return nil, err + } + return append(dst, marshalledObj...), nil +} + +// MarshalSSZ Marshals the block by roots request type into the serialized object. +func (r *LightClientBootstrapReq) MarshalSSZ() ([]byte, error) { + return r[:], nil +} + +// SizeSSZ returns the size of the serialized representation. +func (r *LightClientBootstrapReq) SizeSSZ() int { + return fieldparams.RootLength +} + +// UnmarshalSSZ unmarshals the provided bytes buffer into the +// block by roots request object. +func (r *LightClientBootstrapReq) UnmarshalSSZ(buf []byte) error { + bufLen := len(buf) + if bufLen != fieldparams.RootLength { + return errors.Errorf("expected buffer with length of %d but received length %d", fieldparams.RootLength, bufLen) + } + copy(r[:], buf) + return nil +} diff --git a/beacon-chain/rpc/BUILD.bazel b/beacon-chain/rpc/BUILD.bazel index b4ed6de03633..c2d287110849 100644 --- a/beacon-chain/rpc/BUILD.bazel +++ b/beacon-chain/rpc/BUILD.bazel @@ -23,6 +23,7 @@ go_library( "//beacon-chain/db:go_default_library", "//beacon-chain/db/filesystem:go_default_library", "//beacon-chain/execution:go_default_library", + "//beacon-chain/light-client:go_default_library", "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/blstoexec:go_default_library", "//beacon-chain/operations/slashings:go_default_library", diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index 14d5ad147329..ba35a3bb3e72 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -926,6 +926,7 @@ func (s *Service) lightClientEndpoints(blocker lookup.Blocker, stater lookup.Sta HeadFetcher: s.cfg.HeadFetcher, ChainInfoFetcher: s.cfg.ChainInfoFetcher, BeaconDB: s.cfg.BeaconDB, + LCStore: s.cfg.LCStore, } const namespace = "lightclient" diff --git a/beacon-chain/rpc/eth/light-client/BUILD.bazel b/beacon-chain/rpc/eth/light-client/BUILD.bazel index be1c31a0e3d0..85ede1dbd22b 100644 --- a/beacon-chain/rpc/eth/light-client/BUILD.bazel +++ b/beacon-chain/rpc/eth/light-client/BUILD.bazel @@ -15,6 +15,7 @@ go_library( "//beacon-chain/blockchain:go_default_library", "//beacon-chain/core/light-client:go_default_library", "//beacon-chain/db:go_default_library", + "//beacon-chain/light-client:go_default_library", "//beacon-chain/rpc/eth/shared:go_default_library", "//beacon-chain/rpc/lookup:go_default_library", "//beacon-chain/state:go_default_library", diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 5e550c1855c3..cf1cfb451604 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -3,7 +3,6 @@ package lightclient import ( "context" "fmt" - "math" "net/http" "github.com/ethereum/go-ethereum/common/hexutil" @@ -57,6 +56,7 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques httputil.HandleError(w, "could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError) return } + w.Header().Set(api.VersionHeader, version.String(bootstrap.Version())) if httputil.RespondWithSsz(req) { @@ -148,60 +148,36 @@ func (s *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.R return } - ctx, span := trace.StartSpan(req.Context(), "beacon.GetLightClientFinalityUpdate") + _, span := trace.StartSpan(req.Context(), "beacon.GetLightClientFinalityUpdate") defer span.End() - // Finality update needs super majority of sync committee signatures - minSyncCommitteeParticipants := float64(params.BeaconConfig().MinSyncCommitteeParticipants) - minSignatures := uint64(math.Ceil(minSyncCommitteeParticipants * 2 / 3)) - - block, err := s.suitableBlock(ctx, minSignatures) - if !shared.WriteBlockFetchError(w, block, err) { - return - } - - st, err := s.Stater.StateBySlot(ctx, block.Block().Slot()) - if err != nil { - httputil.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError) - return - } - - attestedRoot := block.Block().ParentRoot() - attestedBlock, err := s.Blocker.Block(ctx, attestedRoot[:]) - if !shared.WriteBlockFetchError(w, block, errors.Wrap(err, "could not get attested block")) { - return - } - attestedSlot := attestedBlock.Block().Slot() - attestedState, err := s.Stater.StateBySlot(ctx, attestedSlot) - if err != nil { - httputil.HandleError(w, "Could not get attested state: "+err.Error(), http.StatusInternalServerError) + update := s.LCStore.LastLCFinalityUpdate + if update == nil { + httputil.HandleError(w, "No light client finality update available", http.StatusNotFound) return } - var finalizedBlock interfaces.ReadOnlySignedBeaconBlock - finalizedCheckpoint := attestedState.FinalizedCheckpoint() - if finalizedCheckpoint == nil { - httputil.HandleError(w, "Attested state does not have a finalized checkpoint", http.StatusInternalServerError) - return - } - finalizedRoot := bytesutil.ToBytes32(finalizedCheckpoint.Root) - finalizedBlock, err = s.Blocker.Block(ctx, finalizedRoot[:]) - if !shared.WriteBlockFetchError(w, block, errors.Wrap(err, "could not get finalized block")) { - return - } + w.Header().Set(api.VersionHeader, version.String(update.Version())) - update, err := newLightClientFinalityUpdateFromBeaconState(ctx, s.ChainInfoFetcher.CurrentSlot(), st, block, attestedState, attestedBlock, finalizedBlock) - if err != nil { - httputil.HandleError(w, "Could not get light client finality update: "+err.Error(), http.StatusInternalServerError) - return - } - - response := &structs.LightClientFinalityUpdateResponse{ - Version: version.String(attestedState.Version()), - Data: update, + if httputil.RespondWithSsz(req) { + ssz, err := update.MarshalSSZ() + if err != nil { + httputil.HandleError(w, "could not marshal update to SSZ: "+err.Error(), http.StatusInternalServerError) + return + } + httputil.WriteSsz(w, ssz, "light_client_finality_update.ssz") + } else { + data, err := structs.LightClientFinalityUpdateFromConsensus(update) + if err != nil { + httputil.HandleError(w, "could not marshal update to JSON: "+err.Error(), http.StatusInternalServerError) + return + } + response := &structs.LightClientFinalityUpdateResponse{ + Version: version.String(update.Version()), + Data: data, + } + httputil.WriteJson(w, response) } - - httputil.WriteJson(w, response) } // GetLightClientOptimisticUpdate - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/optimistic_update.yaml @@ -211,47 +187,36 @@ func (s *Server) GetLightClientOptimisticUpdate(w http.ResponseWriter, req *http return } - ctx, span := trace.StartSpan(req.Context(), "beacon.GetLightClientOptimisticUpdate") + _, span := trace.StartSpan(req.Context(), "beacon.GetLightClientOptimisticUpdate") defer span.End() - block, err := s.suitableBlock(ctx, params.BeaconConfig().MinSyncCommitteeParticipants) - if !shared.WriteBlockFetchError(w, block, err) { - return - } - st, err := s.Stater.StateBySlot(ctx, block.Block().Slot()) - if err != nil { - httputil.HandleError(w, "could not get state: "+err.Error(), http.StatusInternalServerError) - return - } - attestedRoot := block.Block().ParentRoot() - attestedBlock, err := s.Blocker.Block(ctx, attestedRoot[:]) - if err != nil { - httputil.HandleError(w, "Could not get attested block: "+err.Error(), http.StatusInternalServerError) - return - } - if attestedBlock == nil { - httputil.HandleError(w, "Attested block is nil", http.StatusInternalServerError) - return - } - attestedSlot := attestedBlock.Block().Slot() - attestedState, err := s.Stater.StateBySlot(ctx, attestedSlot) - if err != nil { - httputil.HandleError(w, "Could not get attested state: "+err.Error(), http.StatusInternalServerError) + update := s.LCStore.LastLCOptimisticUpdate + if update == nil { + httputil.HandleError(w, "No light client optimistic update available", http.StatusNotFound) return } - update, err := newLightClientOptimisticUpdateFromBeaconState(ctx, s.ChainInfoFetcher.CurrentSlot(), st, block, attestedState, attestedBlock) - if err != nil { - httputil.HandleError(w, "Could not get light client optimistic update: "+err.Error(), http.StatusInternalServerError) - return - } + w.Header().Set(api.VersionHeader, version.String(update.Version())) - response := &structs.LightClientOptimisticUpdateResponse{ - Version: version.String(attestedState.Version()), - Data: update, + if httputil.RespondWithSsz(req) { + ssz, err := update.MarshalSSZ() + if err != nil { + httputil.HandleError(w, "could not marshal update to SSZ: "+err.Error(), http.StatusInternalServerError) + return + } + httputil.WriteSsz(w, ssz, "light_client_optimistic_update.ssz") + } else { + data, err := structs.LightClientOptimisticUpdateFromConsensus(update) + if err != nil { + httputil.HandleError(w, "could not marshal update to JSON: "+err.Error(), http.StatusInternalServerError) + return + } + response := &structs.LightClientOptimisticUpdateResponse{ + Version: version.String(update.Version()), + Data: data, + } + httputil.WriteJson(w, response) } - - httputil.WriteJson(w, response) } // suitableBlock returns the latest block that satisfies all criteria required for creating a new update diff --git a/beacon-chain/rpc/eth/light-client/server.go b/beacon-chain/rpc/eth/light-client/server.go index 84b061379fc1..2157f2d0ce44 100644 --- a/beacon-chain/rpc/eth/light-client/server.go +++ b/beacon-chain/rpc/eth/light-client/server.go @@ -3,6 +3,7 @@ package lightclient import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain" "github.com/prysmaticlabs/prysm/v5/beacon-chain/db" + light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup" ) @@ -12,4 +13,5 @@ type Server struct { HeadFetcher blockchain.HeadFetcher ChainInfoFetcher blockchain.ChainInfoFetcher BeaconDB db.HeadAccessDatabase + LCStore *light_client.Store } diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index 24212eddff1b..2389c169dff8 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -24,6 +24,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/db" "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filesystem" "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" + light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/blstoexec" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" @@ -121,6 +122,7 @@ type Config struct { BlobStorage *filesystem.BlobStorage TrackedValidatorsCache *cache.TrackedValidatorsCache PayloadIDCache *cache.PayloadIDCache + LCStore *light_client.Store } // NewService instantiates a new RPC service instance that will diff --git a/beacon-chain/sync/BUILD.bazel b/beacon-chain/sync/BUILD.bazel index f31f27c12072..8d9545f7fbf4 100644 --- a/beacon-chain/sync/BUILD.bazel +++ b/beacon-chain/sync/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "rpc_blob_sidecars_by_root.go", "rpc_chunked_response.go", "rpc_goodbye.go", + "rpc_light_client.go", "rpc_metadata.go", "rpc_ping.go", "rpc_send_request.go", @@ -38,6 +39,7 @@ go_library( "subscriber_blob_sidecar.go", "subscriber_bls_to_execution_change.go", "subscriber_handlers.go", + "subscriber_light_client.go", "subscriber_sync_committee_message.go", "subscriber_sync_contribution_proof.go", "subscription_topic_handler.go", @@ -48,6 +50,7 @@ go_library( "validate_beacon_blocks.go", "validate_blob.go", "validate_bls_to_execution_change.go", + "validate_light_client.go", "validate_proposer_slashing.go", "validate_sync_committee_message.go", "validate_sync_contribution_proof.go", @@ -79,6 +82,7 @@ go_library( "//beacon-chain/db/filesystem:go_default_library", "//beacon-chain/db/filters:go_default_library", "//beacon-chain/execution:go_default_library", + "//beacon-chain/light-client:go_default_library", "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/blstoexec:go_default_library", "//beacon-chain/operations/slashings:go_default_library", @@ -102,6 +106,7 @@ go_library( "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/light-client:go_default_library", "//consensus-types/primitives:go_default_library", "//consensus-types/wrapper:go_default_library", "//container/leaky-bucket:go_default_library", @@ -111,6 +116,7 @@ go_library( "//encoding/bytesutil:go_default_library", "//encoding/ssz/equality:go_default_library", "//io/file:go_default_library", + "//math:go_default_library", "//monitoring/tracing:go_default_library", "//monitoring/tracing/trace:go_default_library", "//network/forks:go_default_library", diff --git a/beacon-chain/sync/decode_pubsub.go b/beacon-chain/sync/decode_pubsub.go index 1ec9d079448a..19ae91093093 100644 --- a/beacon-chain/sync/decode_pubsub.go +++ b/beacon-chain/sync/decode_pubsub.go @@ -87,6 +87,10 @@ func extractValidDataTypeFromTopic(topic string, digest []byte, clock *startup.C return extractDataTypeFromTypeMap(types.AttestationMap, digest, clock) case p2p.AggregateAndProofSubnetTopicFormat: return extractDataTypeFromTypeMap(types.AggregateAttestationMap, digest, clock) + case p2p.LightClientOptimisticUpdateTopicFormat: + return extractDataTypeFromTypeMap(types.LightClientOptimisticUpdateMap, digest, clock) + case p2p.LightClientFinalityUpdateTopicFormat: + return extractDataTypeFromTypeMap(types.LightClientFinalityUpdateMap, digest, clock) } return nil, nil } diff --git a/beacon-chain/sync/options.go b/beacon-chain/sync/options.go index e29903f9312c..376b311347e5 100644 --- a/beacon-chain/sync/options.go +++ b/beacon-chain/sync/options.go @@ -9,6 +9,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/db" "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filesystem" "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" + light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/blstoexec" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" @@ -188,3 +189,11 @@ func WithAvailableBlocker(avb coverage.AvailableBlocker) Option { return nil } } + +// WithLightClientStore allows the sync package to access light client data. +func WithLightClientStore(lcs *light_client.Store) Option { + return func(s *Service) error { + s.lcStore = lcs + return nil + } +} diff --git a/beacon-chain/sync/rate_limiter.go b/beacon-chain/sync/rate_limiter.go index 4c1a824dc067..e132ba8b3021 100644 --- a/beacon-chain/sync/rate_limiter.go +++ b/beacon-chain/sync/rate_limiter.go @@ -82,6 +82,12 @@ func newRateLimiter(p2pProvider p2p.P2P) *limiter { topicMap[addEncoding(p2p.RPCBlobSidecarsByRootTopicV2)] = blobCollector topicMap[addEncoding(p2p.RPCBlobSidecarsByRangeTopicV2)] = blobCollector + // Light client requests + topicMap[addEncoding(p2p.RPCLightClientBootstrapTopicV1)] = leakybucket.NewCollector(1, defaultBurstLimit, leakyBucketPeriod, false /* deleteEmptyBuckets */) + topicMap[addEncoding(p2p.RPCLightClientUpdatesByRangeTopicV1)] = leakybucket.NewCollector(1, defaultBurstLimit, leakyBucketPeriod, false /* deleteEmptyBuckets */) + topicMap[addEncoding(p2p.RPCLightClientOptimisticUpdateTopicV1)] = leakybucket.NewCollector(1, defaultBurstLimit, leakyBucketPeriod, false /* deleteEmptyBuckets */) + topicMap[addEncoding(p2p.RPCLightClientFinalityUpdateTopicV1)] = leakybucket.NewCollector(1, defaultBurstLimit, leakyBucketPeriod, false /* deleteEmptyBuckets */) + // General topic for all rpc requests. topicMap[rpcLimiterTopic] = leakybucket.NewCollector(5, defaultBurstLimit*2, leakyBucketPeriod, false /* deleteEmptyBuckets */) diff --git a/beacon-chain/sync/rpc.go b/beacon-chain/sync/rpc.go index c04c8621ea03..80de0469350b 100644 --- a/beacon-chain/sync/rpc.go +++ b/beacon-chain/sync/rpc.go @@ -71,12 +71,16 @@ func (s *Service) rpcHandlerByTopicFromFork(forkIndex int) (map[string]rpcHandle // Altair: https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/p2p-interface.md#messages if forkIndex >= version.Altair { return map[string]rpcHandler{ - p2p.RPCStatusTopicV1: s.statusRPCHandler, - p2p.RPCGoodByeTopicV1: s.goodbyeRPCHandler, - p2p.RPCBlocksByRangeTopicV2: s.beaconBlocksByRangeRPCHandler, // Modified in Altair - p2p.RPCBlocksByRootTopicV2: s.beaconBlocksRootRPCHandler, // Modified in Altair - p2p.RPCPingTopicV1: s.pingHandler, - p2p.RPCMetaDataTopicV2: s.metaDataHandler, // Modified in Altair + p2p.RPCStatusTopicV1: s.statusRPCHandler, + p2p.RPCGoodByeTopicV1: s.goodbyeRPCHandler, + p2p.RPCBlocksByRangeTopicV2: s.beaconBlocksByRangeRPCHandler, // Modified in Altair + p2p.RPCBlocksByRootTopicV2: s.beaconBlocksRootRPCHandler, // Modified in Altair + p2p.RPCPingTopicV1: s.pingHandler, + p2p.RPCMetaDataTopicV2: s.metaDataHandler, // Modified in Altair + p2p.RPCLightClientBootstrapTopicV1: s.lightClientBootstrapRPCHandler, // Added in Altair + p2p.RPCLightClientUpdatesByRangeTopicV1: s.lightClientUpdatesByRangeRPCHandler, // Added in Altair + p2p.RPCLightClientFinalityUpdateTopicV1: s.lightClientFinalityUpdateRPCHandler, // Added in Altair + p2p.RPCLightClientOptimisticUpdateTopicV1: s.lightClientOptimisticUpdateRPCHandler, // Added in Altair }, nil } @@ -246,9 +250,12 @@ func (s *Service) registerRPC(baseTopic string, handle rpcHandler) { // Increment message received counter. messageReceivedCounter.WithLabelValues(topic).Inc() - // since metadata requests do not have any data in the payload, we + // since some requests do not have any data in the payload, we // do not decode anything. - if baseTopic == p2p.RPCMetaDataTopicV1 || baseTopic == p2p.RPCMetaDataTopicV2 { + if baseTopic == p2p.RPCMetaDataTopicV1 || + baseTopic == p2p.RPCMetaDataTopicV2 || + baseTopic == p2p.RPCLightClientFinalityUpdateTopicV1 || + baseTopic == p2p.RPCLightClientOptimisticUpdateTopicV1 { if err := handle(ctx, base, stream); err != nil { messageFailedProcessingCounter.WithLabelValues(topic).Inc() if !errors.Is(err, p2ptypes.ErrWrongForkDigestVersion) { diff --git a/beacon-chain/sync/rpc_chunked_response.go b/beacon-chain/sync/rpc_chunked_response.go index 762b8d0f42ae..9acd12e05693 100644 --- a/beacon-chain/sync/rpc_chunked_response.go +++ b/beacon-chain/sync/rpc_chunked_response.go @@ -161,3 +161,98 @@ func WriteBlobSidecarChunk(stream libp2pcore.Stream, tor blockchain.TemporalOrac _, err = encoding.EncodeWithMaxLength(stream, sidecar) return err } + +func WriteLightClientBootstrapChunk( + stream libp2pcore.Stream, + tor blockchain.TemporalOracle, + encoding encoder.NetworkEncoding, + bootstrap interfaces.LightClientBootstrap, +) error { + if _, err := stream.Write([]byte{responseCodeSuccess}); err != nil { + return err + } + + valRoot := tor.GenesisValidatorsRoot() + digest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(bootstrap.Header().Beacon().Slot), valRoot[:]) + if err != nil { + return err + } + obtainedCtx := digest[:] + + if err = writeContextToStream(obtainedCtx, stream); err != nil { + return err + } + _, err = encoding.EncodeWithMaxLength(stream, bootstrap) + return err +} + +func WriteLightClientUpdateChunk( + stream libp2pcore.Stream, + tor blockchain.TemporalOracle, + encoding encoder.NetworkEncoding, + update interfaces.LightClientUpdate, +) error { + if _, err := stream.Write([]byte{responseCodeSuccess}); err != nil { + return err + } + + valRoot := tor.GenesisValidatorsRoot() + digest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(update.AttestedHeader().Beacon().Slot), valRoot[:]) + if err != nil { + return err + } + obtainedCtx := digest[:] + + if err = writeContextToStream(obtainedCtx, stream); err != nil { + return err + } + _, err = encoding.EncodeWithMaxLength(stream, update) + return err +} + +func WriteLightClientFinalityUpdateChunk( + stream libp2pcore.Stream, + tor blockchain.TemporalOracle, + encoding encoder.NetworkEncoding, + update interfaces.LightClientFinalityUpdate, +) error { + if _, err := stream.Write([]byte{responseCodeSuccess}); err != nil { + return err + } + + valRoot := tor.GenesisValidatorsRoot() + digest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(update.AttestedHeader().Beacon().Slot), valRoot[:]) + if err != nil { + return err + } + obtainedCtx := digest[:] + + if err = writeContextToStream(obtainedCtx, stream); err != nil { + return err + } + _, err = encoding.EncodeWithMaxLength(stream, update) + return err +} +func WriteLightClientOptimisticUpdateChunk( + stream libp2pcore.Stream, + tor blockchain.TemporalOracle, + encoding encoder.NetworkEncoding, + update interfaces.LightClientOptimisticUpdate, +) error { + if _, err := stream.Write([]byte{responseCodeSuccess}); err != nil { + return err + } + + valRoot := tor.GenesisValidatorsRoot() + digest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(update.AttestedHeader().Beacon().Slot), valRoot[:]) + if err != nil { + return err + } + obtainedCtx := digest[:] + + if err = writeContextToStream(obtainedCtx, stream); err != nil { + return err + } + _, err = encoding.EncodeWithMaxLength(stream, update) + return err +} diff --git a/beacon-chain/sync/rpc_light_client.go b/beacon-chain/sync/rpc_light_client.go new file mode 100644 index 000000000000..71d392daf5e0 --- /dev/null +++ b/beacon-chain/sync/rpc_light_client.go @@ -0,0 +1,219 @@ +package sync + +import ( + "context" + "fmt" + + libp2pcore "github.com/libp2p/go-libp2p/core" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/types" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/math" + "github.com/prysmaticlabs/prysm/v5/monitoring/tracing" + "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" +) + +// lightClientBootstrapRPCHandler handles the /eth2/beacon_chain/req/light_client_bootstrap/1/ RPC request. +func (s *Service) lightClientBootstrapRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error { + ctx, span := trace.StartSpan(ctx, "sync.lightClientBootstrapRPCHandler") + defer span.End() + ctx, cancel := context.WithTimeout(ctx, ttfbTimeout) + defer cancel() + + // TODO: What should we log? + log := log.WithField("handler", p2p.LightClientBootstrapName[1:]) // slice the leading slash off the name var + + log.Info("LC: lightClientBootstrapRPCHandler invoked") + + SetRPCStreamDeadlines(stream) + if err := s.rateLimiter.validateRequest(stream, 1); err != nil { + log.WithError(err).Error("LC: s.rateLimiter.validateRequest") + return err + } + s.rateLimiter.add(stream, 1) + + rawMsg, ok := msg.(*types.LightClientBootstrapReq) + if !ok { + log.Error("Message is not *types.LightClientBootstrapReq") + return fmt.Errorf("message is not type %T", &types.LightClientBootstrapReq{}) + } + blkRoot := *rawMsg + + bootstrap, err := s.cfg.beaconDB.LightClientBootstrap(ctx, blkRoot[:]) + if err != nil { + s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream) + tracing.AnnotateError(span, err) + log.WithError(err).Error("LC: s.cfg.beaconDB.LightClientBootstrap") + return err + } + if bootstrap == nil { + s.writeErrorResponseToStream(responseCodeResourceUnavailable, types.ErrResourceUnavailable.Error(), stream) + tracing.AnnotateError(span, err) + log.WithError(err).Error(fmt.Sprintf("LC: nil bootstrap for root %#x", blkRoot)) + return err + } + + SetStreamWriteDeadline(stream, defaultWriteDuration) + if err = WriteLightClientBootstrapChunk(stream, s.cfg.clock, s.cfg.p2p.Encoding(), bootstrap); err != nil { + s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream) + tracing.AnnotateError(span, err) + log.WithError(err).Error("LC: WriteLightClientBootstrapChunk") + return err + } + + log.Info("LC: lightClientBootstrapRPCHandler completed") + + closeStream(stream, log) + return nil +} + +// lightClientUpdatesByRangeRPCHandler handles the /eth2/beacon_chain/req/light_client_updates_by_range/1/ RPC request. +func (s *Service) lightClientUpdatesByRangeRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error { + ctx, span := trace.StartSpan(ctx, "sync.lightClientUpdatesByRangeRPCHandler") + defer span.End() + ctx, cancel := context.WithTimeout(ctx, ttfbTimeout) + defer cancel() + + // TODO: What should we log? + log := log.WithField("handler", p2p.LightClientUpdatesByRangeName[1:]) // slice the leading slash off the name var + + log.Info("LC: lightClientUpdatesByRangeRPCHandler invoked") + + SetRPCStreamDeadlines(stream) + if err := s.rateLimiter.validateRequest(stream, 1); err != nil { + log.WithError(err).Error("LC: s.rateLimiter.validateRequest") + return err + } + s.rateLimiter.add(stream, 1) + + r, ok := msg.(*eth.LightClientUpdatesByRangeReq) + if !ok { + log.Error("Message is not *eth.LightClientUpdatesByRangeReq") + return fmt.Errorf("message is not type %T", ð.LightClientUpdatesByRangeReq{}) + } + + if r.Count == 0 { + s.writeErrorResponseToStream(responseCodeInvalidRequest, "count is 0", stream) + s.cfg.p2p.Peers().Scorers().BadResponsesScorer().Increment(stream.Conn().RemotePeer()) + log.Error("LC: Count is 0") + // TODO: Is returning nil OK? + return nil + } + + if r.Count > params.BeaconConfig().MaxRequestLightClientUpdates { + r.Count = params.BeaconConfig().MaxRequestLightClientUpdates + } + + endPeriod, err := math.Add64(r.StartPeriod, r.Count-1) + if err != nil { + s.writeErrorResponseToStream(responseCodeInvalidRequest, err.Error(), stream) + s.cfg.p2p.Peers().Scorers().BadResponsesScorer().Increment(stream.Conn().RemotePeer()) + tracing.AnnotateError(span, err) + log.WithError(err).Error("LC: End period overflows") + return err + } + + log.Infof("LC: requesting updates by range (StartPeriod: %d, EndPeriod: %d)", r.StartPeriod, r.StartPeriod+r.Count-1) + + updates, err := s.cfg.beaconDB.LightClientUpdates(ctx, r.StartPeriod, endPeriod) + if err != nil { + s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream) + tracing.AnnotateError(span, err) + log.WithError(err).Error("LC: s.cfg.beaconDB.LightClientUpdates") + return err + } + for _, u := range updates { + SetStreamWriteDeadline(stream, defaultWriteDuration) + if err = WriteLightClientUpdateChunk(stream, s.cfg.clock, s.cfg.p2p.Encoding(), u); err != nil { + s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream) + tracing.AnnotateError(span, err) + log.WithError(err).Error("LC: WriteLightClientUpdateChunk") + return err + } + s.rateLimiter.add(stream, 1) + } + + log.Info("LC: lightClientUpdatesByRangeRPCHandler completed") + + closeStream(stream, log) + return nil +} + +// lightClientFinalityUpdateRPCHandler handles the /eth2/beacon_chain/req/light_client_finality_update/1/ RPC request. +func (s *Service) lightClientFinalityUpdateRPCHandler(ctx context.Context, _ interface{}, stream libp2pcore.Stream) error { + ctx, span := trace.StartSpan(ctx, "sync.lightClientFinalityUpdateRPCHandler") + defer span.End() + _, cancel := context.WithTimeout(ctx, ttfbTimeout) + defer cancel() + + // TODO: What should we log? + log := log.WithField("handler", p2p.LightClientFinalityUpdateName[1:]) // slice the leading slash off the name var + + log.Info("LC: lightClientFinalityUpdateRPCHandler invoked") + + SetRPCStreamDeadlines(stream) + if err := s.rateLimiter.validateRequest(stream, 1); err != nil { + log.WithError(err).Error("s.rateLimiter.validateRequest") + return err + } + s.rateLimiter.add(stream, 1) + + if s.lcStore.LastLCFinalityUpdate == nil { + s.writeErrorResponseToStream(responseCodeResourceUnavailable, types.ErrResourceUnavailable.Error(), stream) + log.Error("LC: No finality update available") + return nil + } + + SetStreamWriteDeadline(stream, defaultWriteDuration) + if err := WriteLightClientFinalityUpdateChunk(stream, s.cfg.clock, s.cfg.p2p.Encoding(), s.lcStore.LastLCFinalityUpdate); err != nil { + s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream) + tracing.AnnotateError(span, err) + log.WithError(err).Error("LC: WriteLightClientFinalityUpdateChunk") + return err + } + + log.Info("LC: lightClientFinalityUpdateRPCHandler completed") + + closeStream(stream, log) + return nil +} + +// lightClientOptimisticUpdateRPCHandler handles the /eth2/beacon_chain/req/light_client_optimistic_update/1/ RPC request. +func (s *Service) lightClientOptimisticUpdateRPCHandler(ctx context.Context, _ interface{}, stream libp2pcore.Stream) error { + ctx, span := trace.StartSpan(ctx, "sync.lightClientOptimisticUpdateRPCHandler") + defer span.End() + _, cancel := context.WithTimeout(ctx, ttfbTimeout) + defer cancel() + + // TODO: What should we log? + log := log.WithField("handler", p2p.LightClientOptimisticUpdateName[1:]) // slice the leading slash off the name var + + log.Info("LC: lightClientOptimisticUpdateRPCHandler invoked") + + SetRPCStreamDeadlines(stream) + if err := s.rateLimiter.validateRequest(stream, 1); err != nil { + log.WithError(err).Error("s.rateLimiter.validateRequest") + return err + } + s.rateLimiter.add(stream, 1) + + if s.lcStore.LastLCOptimisticUpdate == nil { + s.writeErrorResponseToStream(responseCodeResourceUnavailable, types.ErrResourceUnavailable.Error(), stream) + log.Error("LC: No optimistic update available") + return nil + } + + SetStreamWriteDeadline(stream, defaultWriteDuration) + if err := WriteLightClientOptimisticUpdateChunk(stream, s.cfg.clock, s.cfg.p2p.Encoding(), s.lcStore.LastLCOptimisticUpdate); err != nil { + s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream) + tracing.AnnotateError(span, err) + log.WithError(err).Error("LC: WriteLightClientOptimisticUpdateChunk") + return err + } + + log.Info("LC: lightClientOptimisticUpdateRPCHandler completed") + + closeStream(stream, log) + return nil +} diff --git a/beacon-chain/sync/service.go b/beacon-chain/sync/service.go index 16a243440c8d..3f80def01560 100644 --- a/beacon-chain/sync/service.go +++ b/beacon-chain/sync/service.go @@ -28,6 +28,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/db" "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filesystem" "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" + light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/blstoexec" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" @@ -164,6 +165,7 @@ type Service struct { newBlobVerifier verification.NewBlobVerifier availableBlocker coverage.AvailableBlocker ctxMap ContextByteVersions + lcStore *light_client.Store } // NewService initializes new regular sync service. diff --git a/beacon-chain/sync/subscriber.go b/beacon-chain/sync/subscriber.go index 69afe97eb06c..440b17fc8d85 100644 --- a/beacon-chain/sync/subscriber.go +++ b/beacon-chain/sync/subscriber.go @@ -133,6 +133,18 @@ func (s *Service) registerSubscribers(epoch primitives.Epoch, digest [4]byte) { s.activeSyncSubnetIndices, func(currentSlot primitives.Slot) []uint64 { return []uint64{} }, ) + s.subscribe( + p2p.LightClientFinalityUpdateTopicFormat, + s.validateLightClientFinalityUpdate, + s.lightClientFinalityUpdateSubscriber, + digest, + ) + s.subscribe( + p2p.LightClientOptimisticUpdateTopicFormat, + s.validateLightClientOptimisticUpdate, + s.lightClientOptimisticUpdateSubscriber, + digest, + ) } // New gossip topic in Capella diff --git a/beacon-chain/sync/subscriber_light_client.go b/beacon-chain/sync/subscriber_light_client.go new file mode 100644 index 000000000000..de312a7520e3 --- /dev/null +++ b/beacon-chain/sync/subscriber_light_client.go @@ -0,0 +1,44 @@ +package sync + +import ( + "context" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed" + statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state" + light_client "github.com/prysmaticlabs/prysm/v5/consensus-types/light-client" + "google.golang.org/protobuf/proto" +) + +func (s *Service) lightClientFinalityUpdateSubscriber(_ context.Context, msg proto.Message) error { + update, err := light_client.NewWrappedFinalityUpdate(msg) + if err != nil { + return err + } + + log.Info("LC: storing new finality update in p2p subscriber") + s.lcStore.LastLCFinalityUpdate = update + + s.cfg.stateNotifier.StateFeed().Send(&feed.Event{ + Type: statefeed.LightClientFinalityUpdate, + Data: update, + }) + + return nil +} + +func (s *Service) lightClientOptimisticUpdateSubscriber(_ context.Context, msg proto.Message) error { + update, err := light_client.NewWrappedOptimisticUpdate(msg) + if err != nil { + return err + } + + log.Info("LC: storing new optimistic update in p2p subscriber") + s.lcStore.LastLCOptimisticUpdate = update + + s.cfg.stateNotifier.StateFeed().Send(&feed.Event{ + Type: statefeed.LightClientOptimisticUpdate, + Data: update, + }) + + return nil +} diff --git a/beacon-chain/sync/validate_light_client.go b/beacon-chain/sync/validate_light_client.go new file mode 100644 index 000000000000..404913f65e77 --- /dev/null +++ b/beacon-chain/sync/validate_light_client.go @@ -0,0 +1,132 @@ +package sync + +import ( + "context" + "time" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v5/monitoring/tracing" + "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" + "github.com/prysmaticlabs/prysm/v5/time/slots" +) + +func (s *Service) validateLightClientFinalityUpdate(ctx context.Context, pid peer.ID, msg *pubsub.Message) (pubsub.ValidationResult, error) { + // Validation runs on publish (not just subscriptions), so we should approve any message from + // ourselves. + if pid == s.cfg.p2p.PeerID() { + return pubsub.ValidationAccept, nil + } + + // TODO keep? + // The head state will be too far away to validate any execution change. + if s.cfg.initialSync.Syncing() { + return pubsub.ValidationIgnore, nil + } + + _, span := trace.StartSpan(ctx, "sync.validateLightClientFinalityUpdate") + defer span.End() + + log.Info("LC: p2p validateLightClientFinalityUpdate invoked") + + m, err := s.decodePubsubMessage(msg) + if err != nil { + tracing.AnnotateError(span, err) + return pubsub.ValidationReject, err + } + + update, ok := m.(interfaces.LightClientFinalityUpdate) + if !ok { + return pubsub.ValidationReject, errWrongMessage + } + + maxActiveParticipants := update.SyncAggregate().SyncCommitteeBits.Len() + numActiveParticipants := update.SyncAggregate().SyncCommitteeBits.Count() + hasSupermajority := numActiveParticipants*3 >= maxActiveParticipants*2 + + last := s.lcStore.LastLCFinalityUpdate + if last != nil { + // [IGNORE] The finalized_header.beacon.slot is greater than that of all previously forwarded finality_updates, + // or it matches the highest previously forwarded slot and also has a sync_aggregate indicating supermajority (> 2/3) + // sync committee participation while the previously forwarded finality_update for that slot did not indicate supermajority + slot := last.FinalizedHeader().Beacon().Slot + lastMaxActiveParticipants := last.SyncAggregate().SyncCommitteeBits.Len() + lastNumActiveParticipants := last.SyncAggregate().SyncCommitteeBits.Count() + lastHasSupermajority := lastNumActiveParticipants*3 >= lastMaxActiveParticipants*2 + + if update.FinalizedHeader().Beacon().Slot < slot { + return pubsub.ValidationIgnore, nil + } + if update.FinalizedHeader().Beacon().Slot == slot && (lastHasSupermajority || !hasSupermajority) { + return pubsub.ValidationIgnore, nil + } + } + // [IGNORE] The finality_update is received after the block at signature_slot was given enough time + // to propagate through the network -- i.e. validate that one-third of finality_update.signature_slot + // has transpired (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, + // with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) + earliestValidTime := slots.StartTime(uint64(s.cfg.clock.GenesisTime().Unix()), update.FinalizedHeader().Beacon().Slot). + Add(time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot/params.BeaconConfig().IntervalsPerSlot)). + Add(-params.BeaconConfig().MaximumGossipClockDisparityDuration()) + if s.cfg.clock.Now().Before(earliestValidTime) { + return pubsub.ValidationIgnore, nil + } + + msg.ValidatorData = update.Proto() + + return pubsub.ValidationAccept, nil +} + +func (s *Service) validateLightClientOptimisticUpdate(ctx context.Context, pid peer.ID, msg *pubsub.Message) (pubsub.ValidationResult, error) { + // Validation runs on publish (not just subscriptions), so we should approve any message from + // ourselves. + if pid == s.cfg.p2p.PeerID() { + return pubsub.ValidationAccept, nil + } + + // TODO keep? + // The head state will be too far away to validate any execution change. + if s.cfg.initialSync.Syncing() { + return pubsub.ValidationIgnore, nil + } + + _, span := trace.StartSpan(ctx, "sync.validateLightClientOptimisticUpdate") + defer span.End() + + log.Info("LC: p2p validateLightClientOptimisticUpdate invoked") + + m, err := s.decodePubsubMessage(msg) + if err != nil { + tracing.AnnotateError(span, err) + return pubsub.ValidationReject, err + } + + update, ok := m.(interfaces.LightClientOptimisticUpdate) + if !ok { + return pubsub.ValidationReject, errWrongMessage + } + + last := s.lcStore.LastLCOptimisticUpdate + if last != nil { + // [IGNORE] The attested_header.beacon.slot is greater than that of all previously forwarded optimistic_updates + if update.AttestedHeader().Beacon().Slot <= last.AttestedHeader().Beacon().Slot { + return pubsub.ValidationIgnore, nil + } + } + // [IGNORE] The optimistic_update is received after the block at signature_slot was given enough time + // to propagate through the network -- i.e. validate that one-third of optimistic_update.signature_slot + // has transpired (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, + // with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) + earliestValidTime := slots.StartTime(uint64(s.cfg.clock.GenesisTime().Unix()), update.AttestedHeader().Beacon().Slot). + Add(time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot/params.BeaconConfig().IntervalsPerSlot)). + Add(-params.BeaconConfig().MaximumGossipClockDisparityDuration()) + if s.cfg.clock.Now().Before(earliestValidTime) { + return pubsub.ValidationIgnore, nil + } + + msg.ValidatorData = update.Proto() + + return pubsub.ValidationAccept, nil +} diff --git a/consensus-types/light-client/update.go b/consensus-types/light-client/update.go index dfa6f1742cf3..09649fe59f0c 100644 --- a/consensus-types/light-client/update.go +++ b/consensus-types/light-client/update.go @@ -33,7 +33,6 @@ func NewWrappedUpdate(m proto.Message) (interfaces.LightClientUpdate, error) { // In addition to the proto object being wrapped, we store some fields that have to be // constructed from the proto, so that we don't have to reconstruct them every time // in getters. - type updateAltair struct { p *pb.LightClientUpdateAltair attestedHeader interfaces.LightClientHeader diff --git a/proto/prysm/v1alpha1/p2p_messages.pb.go b/proto/prysm/v1alpha1/p2p_messages.pb.go index 796dc5d3c038..8a2f36e5d213 100755 --- a/proto/prysm/v1alpha1/p2p_messages.pb.go +++ b/proto/prysm/v1alpha1/p2p_messages.pb.go @@ -537,6 +537,61 @@ func (x *DataColumnSidecarsByRangeRequest) GetColumns() []uint64 { return nil } +type LightClientUpdatesByRangeReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StartPeriod uint64 `protobuf:"varint,1,opt,name=start_period,json=startPeriod,proto3" json:"start_period,omitempty"` + Count uint64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *LightClientUpdatesByRangeReq) Reset() { + *x = LightClientUpdatesByRangeReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_prysm_v1alpha1_p2p_messages_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LightClientUpdatesByRangeReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LightClientUpdatesByRangeReq) ProtoMessage() {} + +func (x *LightClientUpdatesByRangeReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_prysm_v1alpha1_p2p_messages_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LightClientUpdatesByRangeReq.ProtoReflect.Descriptor instead. +func (*LightClientUpdatesByRangeReq) Descriptor() ([]byte, []int) { + return file_proto_prysm_v1alpha1_p2p_messages_proto_rawDescGZIP(), []int{8} +} + +func (x *LightClientUpdatesByRangeReq) GetStartPeriod() uint64 { + if x != nil { + return x.StartPeriod + } + return 0 +} + +func (x *LightClientUpdatesByRangeReq) GetCount() uint64 { + if x != nil { + return x.Count + } + return 0 +} + var File_proto_prysm_v1alpha1_p2p_messages_proto protoreflect.FileDescriptor var file_proto_prysm_v1alpha1_p2p_messages_proto_rawDesc = []byte{ @@ -655,17 +710,23 @@ var file_proto_prysm_v1alpha1_p2p_messages_proto_rawDesc = []byte{ 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x42, 0x07, 0x92, 0xb5, 0x18, 0x03, 0x31, 0x32, 0x38, 0x52, - 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x42, 0x9b, 0x01, 0x0a, 0x19, 0x6f, 0x72, 0x67, - 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x10, 0x50, 0x32, 0x50, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, - 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x3b, 0x65, 0x74, 0x68, 0xaa, 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x45, 0x74, 0x68, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, - 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x45, 0x74, 0x68, 0x5c, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x22, 0x57, 0x0a, 0x1c, 0x4c, 0x69, 0x67, 0x68, + 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x42, 0x79, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x42, 0x9b, 0x01, 0x0a, 0x19, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, + 0x10, 0x50, 0x32, 0x50, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, + 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, + 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x65, 0x74, 0x68, 0xaa, + 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x45, 0x74, 0x68, 0x2e, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x5c, 0x45, 0x74, 0x68, 0x5c, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -680,7 +741,7 @@ func file_proto_prysm_v1alpha1_p2p_messages_proto_rawDescGZIP() []byte { return file_proto_prysm_v1alpha1_p2p_messages_proto_rawDescData } -var file_proto_prysm_v1alpha1_p2p_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_proto_prysm_v1alpha1_p2p_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_proto_prysm_v1alpha1_p2p_messages_proto_goTypes = []interface{}{ (*Status)(nil), // 0: ethereum.eth.v1alpha1.Status (*BeaconBlocksByRangeRequest)(nil), // 1: ethereum.eth.v1alpha1.BeaconBlocksByRangeRequest @@ -690,6 +751,7 @@ var file_proto_prysm_v1alpha1_p2p_messages_proto_goTypes = []interface{}{ (*MetaDataV2)(nil), // 5: ethereum.eth.v1alpha1.MetaDataV2 (*BlobSidecarsByRangeRequest)(nil), // 6: ethereum.eth.v1alpha1.BlobSidecarsByRangeRequest (*DataColumnSidecarsByRangeRequest)(nil), // 7: ethereum.eth.v1alpha1.DataColumnSidecarsByRangeRequest + (*LightClientUpdatesByRangeReq)(nil), // 8: ethereum.eth.v1alpha1.LightClientUpdatesByRangeReq } var file_proto_prysm_v1alpha1_p2p_messages_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -801,6 +863,18 @@ func file_proto_prysm_v1alpha1_p2p_messages_proto_init() { return nil } } + file_proto_prysm_v1alpha1_p2p_messages_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LightClientUpdatesByRangeReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -808,7 +882,7 @@ func file_proto_prysm_v1alpha1_p2p_messages_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_prysm_v1alpha1_p2p_messages_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/prysm/v1alpha1/p2p_messages.proto b/proto/prysm/v1alpha1/p2p_messages.proto index 0ea6a4772760..783d75236a0a 100644 --- a/proto/prysm/v1alpha1/p2p_messages.proto +++ b/proto/prysm/v1alpha1/p2p_messages.proto @@ -101,4 +101,17 @@ message DataColumnSidecarsByRangeRequest { uint64 start_slot = 1 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Slot"]; uint64 count = 2; repeated uint64 columns = 3 [(ethereum.eth.ext.ssz_max) = "128"]; -} \ No newline at end of file +} + +/* + Spec Definition: + ( + start_period: uint64 + count: uint64 + ) +*/ +message LightClientUpdatesByRangeReq { + uint64 start_period = 1; + uint64 count = 2; +} +