From 30a136f1fbd3f62b02369be9878decbfdeb3b648 Mon Sep 17 00:00:00 2001 From: Rupam Dey Date: Thu, 5 Dec 2024 02:52:43 +0530 Subject: [PATCH] save light client updates (diff) (#14683) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update diff * deps * add tests for `SaveLightClientUpdate` * cleanup imports * lint * changelog * fix incorrect arithmetic * check for lightclient feature flag * fix tests * fix `saveLightClientBootstrap` and `saveLightClientUpdate` * replace and with or * move feature check to `postBlockProcess` --------- Co-authored-by: RadosÅ‚aw Kapka --- CHANGELOG.md | 1 + api/server/structs/BUILD.bazel | 3 +- api/server/structs/conversions.go | 7 + api/server/structs/conversions_lightclient.go | 210 +- beacon-chain/blockchain/BUILD.bazel | 1 - beacon-chain/blockchain/chain_info_test.go | 9 +- beacon-chain/blockchain/process_block.go | 5 +- .../blockchain/process_block_helpers.go | 165 +- beacon-chain/blockchain/process_block_test.go | 288 +++ .../blockchain/receive_attestation_test.go | 13 +- beacon-chain/blockchain/receive_block.go | 8 +- beacon-chain/blockchain/service.go | 43 +- beacon-chain/core/light-client/BUILD.bazel | 8 +- beacon-chain/core/light-client/lightclient.go | 750 ++++-- .../core/light-client/lightclient_test.go | 318 +-- beacon-chain/db/iface/BUILD.bazel | 1 - beacon-chain/db/iface/interface.go | 10 +- beacon-chain/db/kv/BUILD.bazel | 6 +- beacon-chain/db/kv/kv.go | 1 + beacon-chain/db/kv/lightclient.go | 193 +- beacon-chain/db/kv/lightclient_test.go | 948 +++----- beacon-chain/db/kv/schema.go | 3 +- beacon-chain/execution/engine_client.go | 2 +- beacon-chain/execution/payload_body.go | 2 +- beacon-chain/rpc/endpoints.go | 9 +- beacon-chain/rpc/eth/events/BUILD.bazel | 2 +- beacon-chain/rpc/eth/events/events.go | 22 +- beacon-chain/rpc/eth/light-client/BUILD.bazel | 10 +- beacon-chain/rpc/eth/light-client/handlers.go | 150 +- .../rpc/eth/light-client/handlers_test.go | 2109 ++++++++--------- beacon-chain/rpc/eth/light-client/helpers.go | 339 +-- .../rpc/eth/light-client/helpers_test.go | 1246 +++++----- beacon-chain/rpc/eth/light-client/server.go | 9 +- consensus-types/blocks/proofs.go | 18 +- consensus-types/interfaces/light_client.go | 5 + consensus-types/light-client/bootstrap.go | 160 +- .../light-client/finality_update.go | 68 + .../light-client/optimistic_update.go | 51 + consensus-types/light-client/update.go | 32 +- testing/util/lightclient.go | 60 +- 40 files changed, 3838 insertions(+), 3447 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b27058a5b7d2..33055932d05d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - P2P: Add logs when a peer is (dis)connected. Add the reason of the disconnection when we initiate it. - Added a Prometheus error counter metric for HTTP requests to track beacon node requests. - Added a Prometheus error counter metric for SSE requests. +- Save light client updates and bootstraps in DB. ### Changed diff --git a/api/server/structs/BUILD.bazel b/api/server/structs/BUILD.bazel index c194d502ee02..54ed0b02a9e3 100644 --- a/api/server/structs/BUILD.bazel +++ b/api/server/structs/BUILD.bazel @@ -36,9 +36,8 @@ go_library( "//math:go_default_library", "//proto/engine/v1:go_default_library", "//proto/eth/v1:go_default_library", - "//proto/eth/v2:go_default_library", - "//proto/migration:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_pkg_errors//:go_default_library", diff --git a/api/server/structs/conversions.go b/api/server/structs/conversions.go index 9ed1f50e4e67..74af3c41aba5 100644 --- a/api/server/structs/conversions.go +++ b/api/server/structs/conversions.go @@ -1546,3 +1546,10 @@ func EventChainReorgFromV1(event *ethv1.EventChainReorg) *ChainReorgEvent { ExecutionOptimistic: event.ExecutionOptimistic, } } + +func SyncAggregateFromConsensus(sa *eth.SyncAggregate) *SyncAggregate { + return &SyncAggregate{ + SyncCommitteeBits: hexutil.Encode(sa.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(sa.SyncCommitteeSignature), + } +} diff --git a/api/server/structs/conversions_lightclient.go b/api/server/structs/conversions_lightclient.go index 50e6281ef9b9..a0479f6e39f9 100644 --- a/api/server/structs/conversions_lightclient.go +++ b/api/server/structs/conversions_lightclient.go @@ -3,125 +3,227 @@ package structs import ( "encoding/json" "fmt" - "strconv" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" - v1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" - v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" - "github.com/prysmaticlabs/prysm/v5/proto/migration" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" ) -func LightClientUpdateFromConsensus(update *v2.LightClientUpdate) (*LightClientUpdate, error) { - attestedHeader, err := lightClientHeaderContainerToJSON(update.AttestedHeader) +func LightClientUpdateFromConsensus(update interfaces.LightClientUpdate) (*LightClientUpdate, error) { + attestedHeader, err := lightClientHeaderToJSON(update.AttestedHeader()) if err != nil { return nil, errors.Wrap(err, "could not marshal attested light client header") } - finalizedHeader, err := lightClientHeaderContainerToJSON(update.FinalizedHeader) + finalizedHeader, err := lightClientHeaderToJSON(update.FinalizedHeader()) if err != nil { return nil, errors.Wrap(err, "could not marshal finalized light client header") } + var scBranch [][32]byte + var finalityBranch [][32]byte + if update.Version() >= version.Electra { + scb, err := update.NextSyncCommitteeBranchElectra() + if err != nil { + return nil, err + } + scBranch = scb[:] + fb, err := update.FinalityBranchElectra() + if err != nil { + return nil, err + } + finalityBranch = fb[:] + } else { + scb, err := update.NextSyncCommitteeBranch() + if err != nil { + return nil, err + } + scBranch = scb[:] + fb, err := update.FinalityBranch() + if err != nil { + return nil, err + } + finalityBranch = fb[:] + } + return &LightClientUpdate{ AttestedHeader: attestedHeader, - NextSyncCommittee: SyncCommitteeFromConsensus(migration.V2SyncCommitteeToV1Alpha1(update.NextSyncCommittee)), - NextSyncCommitteeBranch: branchToJSON(update.NextSyncCommitteeBranch), + NextSyncCommittee: SyncCommitteeFromConsensus(update.NextSyncCommittee()), + NextSyncCommitteeBranch: branchToJSON(scBranch), FinalizedHeader: finalizedHeader, - FinalityBranch: branchToJSON(update.FinalityBranch), - SyncAggregate: syncAggregateToJSON(update.SyncAggregate), - SignatureSlot: strconv.FormatUint(uint64(update.SignatureSlot), 10), + FinalityBranch: branchToJSON(finalityBranch), + SyncAggregate: SyncAggregateFromConsensus(update.SyncAggregate()), + SignatureSlot: fmt.Sprintf("%d", update.SignatureSlot()), }, nil } -func LightClientFinalityUpdateFromConsensus(update *v2.LightClientFinalityUpdate) (*LightClientFinalityUpdate, error) { - attestedHeader, err := lightClientHeaderContainerToJSON(update.AttestedHeader) +func LightClientFinalityUpdateFromConsensus(update interfaces.LightClientFinalityUpdate) (*LightClientFinalityUpdate, error) { + attestedHeader, err := lightClientHeaderToJSON(update.AttestedHeader()) if err != nil { return nil, errors.Wrap(err, "could not marshal attested light client header") } - finalizedHeader, err := lightClientHeaderContainerToJSON(update.FinalizedHeader) + finalizedHeader, err := lightClientHeaderToJSON(update.FinalizedHeader()) if err != nil { return nil, errors.Wrap(err, "could not marshal finalized light client header") } + var finalityBranch [][32]byte + if update.Version() >= version.Electra { + b, err := update.FinalityBranchElectra() + if err != nil { + return nil, err + } + finalityBranch = b[:] + } else { + b, err := update.FinalityBranch() + if err != nil { + return nil, err + } + finalityBranch = b[:] + } + return &LightClientFinalityUpdate{ AttestedHeader: attestedHeader, FinalizedHeader: finalizedHeader, - FinalityBranch: branchToJSON(update.FinalityBranch), - SyncAggregate: syncAggregateToJSON(update.SyncAggregate), - SignatureSlot: strconv.FormatUint(uint64(update.SignatureSlot), 10), + FinalityBranch: branchToJSON(finalityBranch), + SyncAggregate: SyncAggregateFromConsensus(update.SyncAggregate()), + SignatureSlot: fmt.Sprintf("%d", update.SignatureSlot()), }, nil } -func LightClientOptimisticUpdateFromConsensus(update *v2.LightClientOptimisticUpdate) (*LightClientOptimisticUpdate, error) { - attestedHeader, err := lightClientHeaderContainerToJSON(update.AttestedHeader) +func LightClientOptimisticUpdateFromConsensus(update interfaces.LightClientOptimisticUpdate) (*LightClientOptimisticUpdate, error) { + attestedHeader, err := lightClientHeaderToJSON(update.AttestedHeader()) if err != nil { return nil, errors.Wrap(err, "could not marshal attested light client header") } return &LightClientOptimisticUpdate{ AttestedHeader: attestedHeader, - SyncAggregate: syncAggregateToJSON(update.SyncAggregate), - SignatureSlot: strconv.FormatUint(uint64(update.SignatureSlot), 10), + SyncAggregate: SyncAggregateFromConsensus(update.SyncAggregate()), + SignatureSlot: fmt.Sprintf("%d", update.SignatureSlot()), }, nil } -func branchToJSON(branchBytes [][]byte) []string { +func branchToJSON[S [][32]byte](branchBytes S) []string { if branchBytes == nil { return nil } branch := make([]string, len(branchBytes)) for i, root := range branchBytes { - branch[i] = hexutil.Encode(root) + branch[i] = hexutil.Encode(root[:]) } return branch } -func syncAggregateToJSON(input *v1.SyncAggregate) *SyncAggregate { - return &SyncAggregate{ - SyncCommitteeBits: hexutil.Encode(input.SyncCommitteeBits), - SyncCommitteeSignature: hexutil.Encode(input.SyncCommitteeSignature), - } -} - -func lightClientHeaderContainerToJSON(container *v2.LightClientHeaderContainer) (json.RawMessage, error) { +func lightClientHeaderToJSON(header interfaces.LightClientHeader) (json.RawMessage, error) { // In the case that a finalizedHeader is nil. - if container == nil { + if header == nil { return nil, nil } - beacon, err := container.GetBeacon() - if err != nil { - return nil, errors.Wrap(err, "could not get beacon block header") - } - - var header any + var result any - switch t := (container.Header).(type) { - case *v2.LightClientHeaderContainer_HeaderAltair: - header = &LightClientHeader{Beacon: BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(beacon))} - case *v2.LightClientHeaderContainer_HeaderCapella: - execution, err := ExecutionPayloadHeaderCapellaFromConsensus(t.HeaderCapella.Execution) + switch v := header.Version(); v { + case version.Altair: + result = &LightClientHeader{Beacon: BeaconBlockHeaderFromConsensus(header.Beacon())} + case version.Capella: + exInterface, err := header.Execution() + if err != nil { + return nil, err + } + ex, ok := exInterface.Proto().(*enginev1.ExecutionPayloadHeaderCapella) + if !ok { + return nil, fmt.Errorf("execution data is not %T", &enginev1.ExecutionPayloadHeaderCapella{}) + } + execution, err := ExecutionPayloadHeaderCapellaFromConsensus(ex) + if err != nil { + return nil, err + } + executionBranch, err := header.ExecutionBranch() if err != nil { return nil, err } - header = &LightClientHeaderCapella{ - Beacon: BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(beacon)), + result = &LightClientHeaderCapella{ + Beacon: BeaconBlockHeaderFromConsensus(header.Beacon()), Execution: execution, - ExecutionBranch: branchToJSON(t.HeaderCapella.ExecutionBranch), + ExecutionBranch: branchToJSON(executionBranch[:]), + } + case version.Deneb: + exInterface, err := header.Execution() + if err != nil { + return nil, err + } + ex, ok := exInterface.Proto().(*enginev1.ExecutionPayloadHeaderDeneb) + if !ok { + return nil, fmt.Errorf("execution data is not %T", &enginev1.ExecutionPayloadHeaderDeneb{}) + } + execution, err := ExecutionPayloadHeaderDenebFromConsensus(ex) + if err != nil { + return nil, err + } + executionBranch, err := header.ExecutionBranch() + if err != nil { + return nil, err + } + result = &LightClientHeaderDeneb{ + Beacon: BeaconBlockHeaderFromConsensus(header.Beacon()), + Execution: execution, + ExecutionBranch: branchToJSON(executionBranch[:]), + } + case version.Electra: + exInterface, err := header.Execution() + if err != nil { + return nil, err + } + ex, ok := exInterface.Proto().(*enginev1.ExecutionPayloadHeaderElectra) + if !ok { + return nil, fmt.Errorf("execution data is not %T", &enginev1.ExecutionPayloadHeaderElectra{}) } - case *v2.LightClientHeaderContainer_HeaderDeneb: - execution, err := ExecutionPayloadHeaderDenebFromConsensus(t.HeaderDeneb.Execution) + execution, err := ExecutionPayloadHeaderElectraFromConsensus(ex) if err != nil { return nil, err } - header = &LightClientHeaderDeneb{ - Beacon: BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(beacon)), + executionBranch, err := header.ExecutionBranch() + if err != nil { + return nil, err + } + result = &LightClientHeaderDeneb{ + Beacon: BeaconBlockHeaderFromConsensus(header.Beacon()), Execution: execution, - ExecutionBranch: branchToJSON(t.HeaderDeneb.ExecutionBranch), + ExecutionBranch: branchToJSON(executionBranch[:]), } default: - return nil, fmt.Errorf("unsupported header type %T", t) + return nil, fmt.Errorf("unsupported header version %s", version.String(v)) } - return json.Marshal(header) + return json.Marshal(result) +} + +func LightClientBootstrapFromConsensus(bootstrap interfaces.LightClientBootstrap) (*LightClientBootstrap, error) { + header, err := lightClientHeaderToJSON(bootstrap.Header()) + if err != nil { + return nil, errors.Wrap(err, "could not marshal light client header") + } + + var scBranch [][32]byte + if bootstrap.Version() >= version.Electra { + b, err := bootstrap.CurrentSyncCommitteeBranchElectra() + if err != nil { + return nil, err + } + scBranch = b[:] + } else { + b, err := bootstrap.CurrentSyncCommitteeBranch() + if err != nil { + return nil, err + } + scBranch = b[:] + } + + return &LightClientBootstrap{ + Header: header, + CurrentSyncCommittee: SyncCommitteeFromConsensus(bootstrap.CurrentSyncCommittee()), + CurrentSyncCommitteeBranch: branchToJSON(scBranch), + }, nil } diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index c12a3d1340bd..2dc6f11a87f9 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -84,7 +84,6 @@ go_library( "//monitoring/tracing/trace:go_default_library", "//proto/engine/v1:go_default_library", "//proto/eth/v1:go_default_library", - "//proto/eth/v2:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1/attestation:go_default_library", "//runtime/version:go_default_library", diff --git a/beacon-chain/blockchain/chain_info_test.go b/beacon-chain/blockchain/chain_info_test.go index b5f92c316e55..e850f36adad5 100644 --- a/beacon-chain/blockchain/chain_info_test.go +++ b/beacon-chain/blockchain/chain_info_test.go @@ -13,7 +13,6 @@ import ( fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" - consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" @@ -39,7 +38,7 @@ func prepareForkchoiceState( payloadHash [32]byte, justified *ethpb.Checkpoint, finalized *ethpb.Checkpoint, -) (state.BeaconState, consensus_blocks.ROBlock, error) { +) (state.BeaconState, blocks.ROBlock, error) { blockHeader := ðpb.BeaconBlockHeader{ ParentRoot: parentRoot[:], } @@ -61,7 +60,7 @@ func prepareForkchoiceState( base.BlockRoots[0] = append(base.BlockRoots[0], blockRoot[:]...) st, err := state_native.InitializeFromProtoBellatrix(base) if err != nil { - return nil, consensus_blocks.ROBlock{}, err + return nil, blocks.ROBlock{}, err } blk := ðpb.SignedBeaconBlockBellatrix{ Block: ðpb.BeaconBlockBellatrix{ @@ -76,9 +75,9 @@ func prepareForkchoiceState( } signed, err := blocks.NewSignedBeaconBlock(blk) if err != nil { - return nil, consensus_blocks.ROBlock{}, err + return nil, blocks.ROBlock{}, err } - roblock, err := consensus_blocks.NewROBlockWithRoot(signed, blockRoot) + roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot) return st, roblock, err } diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go index 39a384fff0d5..80e214667860 100644 --- a/beacon-chain/blockchain/process_block.go +++ b/beacon-chain/blockchain/process_block.go @@ -67,7 +67,10 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { if s.inRegularSync() { defer s.handleSecondFCUCall(cfg, fcuArgs) } - defer s.sendLightClientFeeds(cfg) + if features.Get().EnableLightClient && slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().AltairForkEpoch { + defer s.processLightClientUpdates(cfg) + defer s.saveLightClientUpdate(cfg) + } defer s.sendStateFeedOnBlock(cfg) defer reportProcessingTime(startTime) defer reportAttestationInclusion(cfg.roblock.Block()) diff --git a/beacon-chain/blockchain/process_block_helpers.go b/beacon-chain/blockchain/process_block_helpers.go index fe01f582bfa5..d39e50cb533d 100644 --- a/beacon-chain/blockchain/process_block_helpers.go +++ b/beacon-chain/blockchain/process_block_helpers.go @@ -15,7 +15,6 @@ import ( doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree" forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" - "github.com/prysmaticlabs/prysm/v5/config/features" field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" @@ -24,7 +23,6 @@ import ( "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" mathutil "github.com/prysmaticlabs/prysm/v5/math" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/sirupsen/logrus" @@ -115,64 +113,123 @@ 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.roblock, 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.roblock, finalized, cfg.postState) +func (s *Service) processLightClientUpdates(cfg *postBlockProcessConfig) { + if err := s.processLightClientOptimisticUpdate(cfg.ctx, cfg.roblock, cfg.postState); err != nil { + log.WithError(err).Error("Failed to process light client optimistic update") + } + if err := s.processLightClientFinalityUpdate(cfg.ctx, cfg.roblock, cfg.postState); err != nil { + log.WithError(err).Error("Failed to process light client finality update") } } -func (s *Service) tryPublishLightClientFinalityUpdate(ctx context.Context, signed interfaces.ReadOnlySignedBeaconBlock, finalized *forkchoicetypes.Checkpoint, postState state.BeaconState) { - if finalized.Epoch <= s.lastPublishedLightClientEpoch { +// saveLightClientUpdate saves the light client update for this block +// if it's better than the already saved one, when feature flag is enabled. +func (s *Service) saveLightClientUpdate(cfg *postBlockProcessConfig) { + attestedRoot := cfg.roblock.Block().ParentRoot() + attestedBlock, err := s.getBlock(cfg.ctx, attestedRoot) + if err != nil { + log.WithError(err).Error("Saving light client update failed: Could not get attested block") + return + } + if attestedBlock == nil || attestedBlock.IsNil() { + log.Error("Saving light client update failed: Attested block is nil") + return + } + attestedState, err := s.cfg.StateGen.StateByRoot(cfg.ctx, attestedRoot) + if err != nil { + log.WithError(err).Error("Saving light client update failed: Could not get attested state") + return + } + if attestedState == nil || attestedState.IsNil() { + log.Error("Saving light client update failed: Attested state is nil") return } - config := params.BeaconConfig() - if finalized.Epoch < config.AltairForkEpoch { + finalizedRoot := attestedState.FinalizedCheckpoint().Root + finalizedBlock, err := s.getBlock(cfg.ctx, [32]byte(finalizedRoot)) + if err != nil { + log.WithError(err).Error("Saving light client update failed: Could not get finalized block") return } - syncAggregate, err := signed.Block().Body().SyncAggregate() - if err != nil || syncAggregate == nil { + update, err := lightclient.NewLightClientUpdateFromBeaconState( + cfg.ctx, + s.CurrentSlot(), + cfg.postState, + cfg.roblock, + attestedState, + attestedBlock, + finalizedBlock, + ) + if err != nil { + log.WithError(err).Error("Saving light client update failed: Could not create light client update") + return + } + + period := slots.SyncCommitteePeriod(slots.ToEpoch(attestedState.Slot())) + + oldUpdate, err := s.cfg.BeaconDB.LightClientUpdate(cfg.ctx, period) + if err != nil { + log.WithError(err).Error("Saving light client update failed: Could not get current light client update") return } - // LightClientFinalityUpdate needs super majority - if syncAggregate.SyncCommitteeBits.Count()*3 < config.SyncCommitteeSize*2 { + if oldUpdate == nil { + if err := s.cfg.BeaconDB.SaveLightClientUpdate(cfg.ctx, period, update); err != nil { + log.WithError(err).Error("Saving light client update failed: Could not save light client update") + } else { + log.WithField("period", period).Debug("Saving light client update: Saved new update") + } return } - _, err = s.sendLightClientFinalityUpdate(ctx, signed, postState) + isNewUpdateBetter, err := lightclient.IsBetterUpdate(update, oldUpdate) if err != nil { - log.WithError(err).Error("Failed to send light client finality update") + log.WithError(err).Error("Saving light client update failed: Could not compare light client updates") + return + } + + if isNewUpdateBetter { + if err := s.cfg.BeaconDB.SaveLightClientUpdate(cfg.ctx, period, update); err != nil { + log.WithError(err).Error("Saving light client update failed: Could not save light client update") + } else { + log.WithField("period", period).Debug("Saving light client update: Saved new update") + } } else { - s.lastPublishedLightClientEpoch = finalized.Epoch + log.WithField("period", period).Debug("Saving light client update: New update is not better than the current one. Skipping save.") + } +} + +// saveLightClientBootstrap saves a light client bootstrap for this block +// when feature flag is enabled. +func (s *Service) saveLightClientBootstrap(cfg *postBlockProcessConfig) { + blockRoot := cfg.roblock.Root() + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(cfg.ctx, s.CurrentSlot(), cfg.postState, cfg.roblock) + if err != nil { + log.WithError(err).Error("Saving light client bootstrap failed: Could not create light client bootstrap") + return + } + err = s.cfg.BeaconDB.SaveLightClientBootstrap(cfg.ctx, blockRoot[:], bootstrap) + if err != nil { + log.WithError(err).Error("Saving light client bootstrap failed: Could not save light client bootstrap in DB") } } -// 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 +func (s *Service) processLightClientFinalityUpdate( + ctx context.Context, + signed interfaces.ReadOnlySignedBeaconBlock, + postState state.BeaconState, +) error { attestedRoot := signed.Block().ParentRoot() attestedBlock, err := s.cfg.BeaconDB.Block(ctx, attestedRoot) if err != nil { - return 0, errors.Wrap(err, "could not get attested block") + return errors.Wrap(err, "could not get attested block") } attestedState, err := s.cfg.StateGen.StateByRoot(ctx, attestedRoot) if err != nil { - return 0, errors.Wrap(err, "could not get attested state") + return errors.Wrap(err, "could not get attested state") } - // Get finalized block var finalizedBlock interfaces.ReadOnlySignedBeaconBlock finalizedCheckPoint := attestedState.FinalizedCheckpoint() if finalizedCheckPoint != nil { @@ -185,6 +242,7 @@ func (s *Service) sendLightClientFinalityUpdate(ctx context.Context, signed inte update, err := lightclient.NewLightClientFinalityUpdateFromBeaconState( ctx, + postState.Slot(), postState, signed, attestedState, @@ -193,38 +251,31 @@ func (s *Service) sendLightClientFinalityUpdate(ctx context.Context, signed inte ) if err != nil { - return 0, errors.Wrap(err, "could not create light client update") + return errors.Wrap(err, "could not create light client finality update") } - // Return the result - result := ðpbv2.LightClientFinalityUpdateWithVersion{ - Version: ethpbv2.Version(signed.Version()), - Data: update, - } - - // Send event - return s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ + s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ Type: statefeed.LightClientFinalityUpdate, - Data: result, - }), nil + Data: update, + }) + return 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 +func (s *Service) processLightClientOptimisticUpdate(ctx context.Context, signed interfaces.ReadOnlySignedBeaconBlock, + postState state.BeaconState) error { attestedRoot := signed.Block().ParentRoot() attestedBlock, err := s.cfg.BeaconDB.Block(ctx, attestedRoot) if err != nil { - return 0, errors.Wrap(err, "could not get attested block") + return errors.Wrap(err, "could not get attested block") } attestedState, err := s.cfg.StateGen.StateByRoot(ctx, attestedRoot) if err != nil { - return 0, errors.Wrap(err, "could not get attested state") + return errors.Wrap(err, "could not get attested state") } update, err := lightclient.NewLightClientOptimisticUpdateFromBeaconState( ctx, + postState.Slot(), postState, signed, attestedState, @@ -232,19 +283,15 @@ func (s *Service) sendLightClientOptimisticUpdate(ctx context.Context, signed in ) if err != nil { - return 0, errors.Wrap(err, "could not create light client update") + return errors.Wrap(err, "could not create light client optimistic update") } - // Return the result - result := ðpbv2.LightClientOptimisticUpdateWithVersion{ - Version: ethpbv2.Version(signed.Version()), - Data: update, - } - - return s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ + s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ Type: statefeed.LightClientOptimisticUpdate, - Data: result, - }), nil + Data: update, + }) + + return nil } // updateCachesPostBlockProcessing updates the next slot cache and handles the epoch diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index 9b6a99200e76..c6b097f2b829 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -40,6 +40,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" prysmTime "github.com/prysmaticlabs/prysm/v5/time" + "github.com/prysmaticlabs/prysm/v5/time/slots" logTest "github.com/sirupsen/logrus/hooks/test" ) @@ -2502,3 +2503,290 @@ func fakeResult(missing []uint64) map[uint64]struct{} { } return r } + +func TestSaveLightClientUpdate(t *testing.T) { + s, tr := minimalTestService(t) + ctx := tr.ctx + + t.Run("Altair", func(t *testing.T) { + featCfg := &features.Flags{} + featCfg.EnableLightClient = true + reset := features.InitWithReset(featCfg) + + l := util.NewTestLightClient(t).SetupTestAltair() + + s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().AltairForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) + + err := s.cfg.BeaconDB.SaveBlock(ctx, l.AttestedBlock) + require.NoError(t, err) + attestedBlockRoot, err := l.AttestedBlock.Block().HashTreeRoot() + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.AttestedState, attestedBlockRoot) + require.NoError(t, err) + + currentBlockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, l.FinalizedBlock) + require.NoError(t, err) + + cfg := &postBlockProcessConfig{ + ctx: ctx, + roblock: roblock, + postState: l.State, + isValidPayload: true, + } + + s.saveLightClientUpdate(cfg) + + // Check that the light client update is saved + period := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedState.Slot())) + + u, err := s.cfg.BeaconDB.LightClientUpdate(ctx, period) + require.NoError(t, err) + require.NotNil(t, u) + attestedStateRoot, err := l.AttestedState.HashTreeRoot(ctx) + require.NoError(t, err) + require.Equal(t, attestedStateRoot, [32]byte(u.AttestedHeader().Beacon().StateRoot)) + require.Equal(t, u.Version(), version.Altair) + + reset() + }) + + t.Run("Capella", func(t *testing.T) { + featCfg := &features.Flags{} + featCfg.EnableLightClient = true + reset := features.InitWithReset(featCfg) + + l := util.NewTestLightClient(t).SetupTestCapella(false) + + s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().CapellaForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) + + err := s.cfg.BeaconDB.SaveBlock(ctx, l.AttestedBlock) + require.NoError(t, err) + attestedBlockRoot, err := l.AttestedBlock.Block().HashTreeRoot() + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.AttestedState, attestedBlockRoot) + require.NoError(t, err) + + currentBlockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, l.FinalizedBlock) + require.NoError(t, err) + + cfg := &postBlockProcessConfig{ + ctx: ctx, + roblock: roblock, + postState: l.State, + isValidPayload: true, + } + + s.saveLightClientUpdate(cfg) + + // Check that the light client update is saved + period := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedState.Slot())) + u, err := s.cfg.BeaconDB.LightClientUpdate(ctx, period) + require.NoError(t, err) + require.NotNil(t, u) + attestedStateRoot, err := l.AttestedState.HashTreeRoot(ctx) + require.NoError(t, err) + require.Equal(t, attestedStateRoot, [32]byte(u.AttestedHeader().Beacon().StateRoot)) + require.Equal(t, u.Version(), version.Capella) + + reset() + }) + + t.Run("Deneb", func(t *testing.T) { + featCfg := &features.Flags{} + featCfg.EnableLightClient = true + reset := features.InitWithReset(featCfg) + + l := util.NewTestLightClient(t).SetupTestDeneb(false) + + s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().DenebForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) + + err := s.cfg.BeaconDB.SaveBlock(ctx, l.AttestedBlock) + require.NoError(t, err) + attestedBlockRoot, err := l.AttestedBlock.Block().HashTreeRoot() + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.AttestedState, attestedBlockRoot) + require.NoError(t, err) + + currentBlockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, l.FinalizedBlock) + require.NoError(t, err) + + cfg := &postBlockProcessConfig{ + ctx: ctx, + roblock: roblock, + postState: l.State, + isValidPayload: true, + } + + s.saveLightClientUpdate(cfg) + + // Check that the light client update is saved + period := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedState.Slot())) + u, err := s.cfg.BeaconDB.LightClientUpdate(ctx, period) + require.NoError(t, err) + require.NotNil(t, u) + attestedStateRoot, err := l.AttestedState.HashTreeRoot(ctx) + require.NoError(t, err) + require.Equal(t, attestedStateRoot, [32]byte(u.AttestedHeader().Beacon().StateRoot)) + require.Equal(t, u.Version(), version.Deneb) + + reset() + }) +} + +func TestSaveLightClientBootstrap(t *testing.T) { + s, tr := minimalTestService(t) + ctx := tr.ctx + + t.Run("Altair", func(t *testing.T) { + featCfg := &features.Flags{} + featCfg.EnableLightClient = true + reset := features.InitWithReset(featCfg) + + l := util.NewTestLightClient(t).SetupTestAltair() + + s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().AltairForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) + + currentBlockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) + require.NoError(t, err) + + cfg := &postBlockProcessConfig{ + ctx: ctx, + roblock: roblock, + postState: l.State, + isValidPayload: true, + } + + s.saveLightClientBootstrap(cfg) + + // Check that the light client bootstrap is saved + b, err := s.cfg.BeaconDB.LightClientBootstrap(ctx, currentBlockRoot[:]) + require.NoError(t, err) + require.NotNil(t, b) + + stateRoot, err := l.State.HashTreeRoot(ctx) + require.NoError(t, err) + require.Equal(t, stateRoot, [32]byte(b.Header().Beacon().StateRoot)) + require.Equal(t, b.Version(), version.Altair) + + reset() + }) + + t.Run("Capella", func(t *testing.T) { + featCfg := &features.Flags{} + featCfg.EnableLightClient = true + reset := features.InitWithReset(featCfg) + + l := util.NewTestLightClient(t).SetupTestCapella(false) + + s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().CapellaForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) + + currentBlockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) + require.NoError(t, err) + + cfg := &postBlockProcessConfig{ + ctx: ctx, + roblock: roblock, + postState: l.State, + isValidPayload: true, + } + + s.saveLightClientBootstrap(cfg) + + // Check that the light client bootstrap is saved + b, err := s.cfg.BeaconDB.LightClientBootstrap(ctx, currentBlockRoot[:]) + require.NoError(t, err) + require.NotNil(t, b) + + stateRoot, err := l.State.HashTreeRoot(ctx) + require.NoError(t, err) + require.Equal(t, stateRoot, [32]byte(b.Header().Beacon().StateRoot)) + require.Equal(t, b.Version(), version.Capella) + + reset() + }) + + t.Run("Deneb", func(t *testing.T) { + featCfg := &features.Flags{} + featCfg.EnableLightClient = true + reset := features.InitWithReset(featCfg) + + l := util.NewTestLightClient(t).SetupTestDeneb(false) + + s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().DenebForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0) + + currentBlockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + roblock, err := consensusblocks.NewROBlockWithRoot(l.Block, currentBlockRoot) + require.NoError(t, err) + + err = s.cfg.BeaconDB.SaveBlock(ctx, roblock) + require.NoError(t, err) + err = s.cfg.BeaconDB.SaveState(ctx, l.State, currentBlockRoot) + require.NoError(t, err) + + cfg := &postBlockProcessConfig{ + ctx: ctx, + roblock: roblock, + postState: l.State, + isValidPayload: true, + } + + s.saveLightClientBootstrap(cfg) + + // Check that the light client bootstrap is saved + b, err := s.cfg.BeaconDB.LightClientBootstrap(ctx, currentBlockRoot[:]) + require.NoError(t, err) + require.NotNil(t, b) + + stateRoot, err := l.State.HashTreeRoot(ctx) + require.NoError(t, err) + require.Equal(t, stateRoot, [32]byte(b.Header().Beacon().StateRoot)) + require.Equal(t, b.Version(), version.Deneb) + + reset() + }) +} diff --git a/beacon-chain/blockchain/receive_attestation_test.go b/beacon-chain/blockchain/receive_attestation_test.go index 6a905d67c770..0d4ec5698d88 100644 --- a/beacon-chain/blockchain/receive_attestation_test.go +++ b/beacon-chain/blockchain/receive_attestation_test.go @@ -10,7 +10,6 @@ import ( forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" - consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -87,9 +86,7 @@ func TestProcessAttestations_Ok(t *testing.T) { require.NoError(t, err) require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot)) attsToSave := make([]ethpb.Att, len(atts)) - for i, a := range atts { - attsToSave[i] = a - } + copy(attsToSave, atts) require.NoError(t, service.cfg.AttPool.SaveForkchoiceAttestations(attsToSave)) service.processAttestations(ctx, 0) require.Equal(t, 0, len(service.cfg.AttPool.ForkchoiceAttestations())) @@ -119,7 +116,7 @@ func TestService_ProcessAttestationsAndUpdateHead(t *testing.T) { postState, err := service.validateStateTransition(ctx, preState, wsb) require.NoError(t, err) require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState)) - roblock, err := consensus_blocks.NewROBlockWithRoot(wsb, tRoot) + roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot) require.NoError(t, err) require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})) copied, err = service.cfg.StateGen.StateByRoot(ctx, tRoot) @@ -131,9 +128,7 @@ func TestService_ProcessAttestationsAndUpdateHead(t *testing.T) { atts, err := util.GenerateAttestations(copied, pks, 1, 1, false) require.NoError(t, err) attsToSave := make([]ethpb.Att, len(atts)) - for i, a := range atts { - attsToSave[i] = a - } + copy(attsToSave, atts) require.NoError(t, service.cfg.AttPool.SaveForkchoiceAttestations(attsToSave)) // Verify the target is in forkchoice require.Equal(t, true, fcs.HasNode(bytesutil.ToBytes32(atts[0].GetData().BeaconBlockRoot))) @@ -181,7 +176,7 @@ func TestService_UpdateHead_NoAtts(t *testing.T) { postState, err := service.validateStateTransition(ctx, preState, wsb) require.NoError(t, err) require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState)) - roblock, err := consensus_blocks.NewROBlockWithRoot(wsb, tRoot) + roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot) require.NoError(t, err) require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})) require.Equal(t, 2, fcs.NodeCount()) diff --git a/beacon-chain/blockchain/receive_block.go b/beacon-chain/blockchain/receive_block.go index ff8c4d9187e4..6a55bf2660c0 100644 --- a/beacon-chain/blockchain/receive_block.go +++ b/beacon-chain/blockchain/receive_block.go @@ -17,8 +17,6 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" - consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" - consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" @@ -85,7 +83,7 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig } currentCheckpoints := s.saveCurrentCheckpoints(preState) - roblock, err := consensus_blocks.NewROBlockWithRoot(blockCopy, blockRoot) + roblock, err := blocks.NewROBlockWithRoot(blockCopy, blockRoot) if err != nil { return err } @@ -190,7 +188,7 @@ func (s *Service) updateCheckpoints( func (s *Service) validateExecutionAndConsensus( ctx context.Context, preState state.BeaconState, - block consensusblocks.ROBlock, + block blocks.ROBlock, ) (state.BeaconState, bool, error) { preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState) if err != nil { @@ -560,7 +558,7 @@ func (s *Service) sendBlockAttestationsToSlasher(signed interfaces.ReadOnlySigne } // validateExecutionOnBlock notifies the engine of the incoming block execution payload and returns true if the payload is valid -func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header interfaces.ExecutionData, block consensusblocks.ROBlock) (bool, error) { +func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header interfaces.ExecutionData, block blocks.ROBlock) (bool, error) { isValidPayload, err := s.notifyNewPayload(ctx, ver, header, block) if err != nil { s.cfg.ForkChoiceStore.Lock() diff --git a/beacon-chain/blockchain/service.go b/beacon-chain/blockchain/service.go index c984a2f79750..998b9845ee16 100644 --- a/beacon-chain/blockchain/service.go +++ b/beacon-chain/blockchain/service.go @@ -36,9 +36,7 @@ import ( fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" - consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" - "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -49,25 +47,24 @@ import ( // 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 - lastPublishedLightClientEpoch primitives.Epoch + 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 } // config options for the service. @@ -308,7 +305,7 @@ func (s *Service) StartFromSavedState(saved state.BeaconState) error { if err != nil { return errors.Wrap(err, "could not get finalized checkpoint block") } - roblock, err := consensus_blocks.NewROBlockWithRoot(finalizedBlock, fRoot) + roblock, err := blocks.NewROBlockWithRoot(finalizedBlock, fRoot) if err != nil { return err } @@ -524,7 +521,7 @@ func (s *Service) saveGenesisData(ctx context.Context, genesisState state.Beacon s.cfg.ForkChoiceStore.Lock() defer s.cfg.ForkChoiceStore.Unlock() - gb, err := consensus_blocks.NewROBlockWithRoot(genesisBlk, genesisBlkRoot) + gb, err := blocks.NewROBlockWithRoot(genesisBlk, genesisBlkRoot) if err != nil { return err } diff --git a/beacon-chain/core/light-client/BUILD.bazel b/beacon-chain/core/light-client/BUILD.bazel index c7a264c71bb6..ecf6b8f71a75 100644 --- a/beacon-chain/core/light-client/BUILD.bazel +++ b/beacon-chain/core/light-client/BUILD.bazel @@ -6,19 +6,22 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client", visibility = ["//visibility:public"], deps = [ + "//beacon-chain/execution:go_default_library", "//beacon-chain/state:go_default_library", "//config/fieldparams:go_default_library", "//config/params:go_default_library", "//consensus-types: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", "//encoding/ssz:go_default_library", "//proto/engine/v1:go_default_library", - "//proto/eth/v1:go_default_library", - "//proto/eth/v2:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_pkg_errors//:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", ], ) @@ -32,6 +35,7 @@ go_test( "//consensus-types/blocks:go_default_library", "//encoding/ssz:go_default_library", "//proto/engine/v1:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", "@com_github_pkg_errors//:go_default_library", diff --git a/beacon-chain/core/light-client/lightclient.go b/beacon-chain/core/light-client/lightclient.go index 1173ec355331..54932094b27b 100644 --- a/beacon-chain/core/light-client/lightclient.go +++ b/beacon-chain/core/light-client/lightclient.go @@ -4,87 +4,74 @@ import ( "bytes" "context" "fmt" + "reflect" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types" "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/encoding/ssz" - v11 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" - ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" + enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/time/slots" + "google.golang.org/protobuf/proto" ) -const ( - FinalityBranchNumOfLeaves = 6 - executionBranchNumOfLeaves = 4 -) - -func createLightClientFinalityUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientFinalityUpdate { - finalityUpdate := ðpbv2.LightClientFinalityUpdate{ - AttestedHeader: update.AttestedHeader, - FinalizedHeader: update.FinalizedHeader, - FinalityBranch: update.FinalityBranch, - SyncAggregate: update.SyncAggregate, - SignatureSlot: update.SignatureSlot, - } - - return finalityUpdate -} - -func createLightClientOptimisticUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientOptimisticUpdate { - optimisticUpdate := ðpbv2.LightClientOptimisticUpdate{ - AttestedHeader: update.AttestedHeader, - SyncAggregate: update.SyncAggregate, - SignatureSlot: update.SignatureSlot, - } - - return optimisticUpdate -} - func NewLightClientFinalityUpdateFromBeaconState( ctx context.Context, + currentSlot primitives.Slot, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock, attestedState state.BeaconState, attestedBlock interfaces.ReadOnlySignedBeaconBlock, finalizedBlock interfaces.ReadOnlySignedBeaconBlock, -) (*ethpbv2.LightClientFinalityUpdate, error) { - update, err := NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, finalizedBlock) +) (interfaces.LightClientFinalityUpdate, error) { + update, err := NewLightClientUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock, finalizedBlock) if err != nil { return nil, err } - return createLightClientFinalityUpdate(update), nil + return light_client.NewFinalityUpdateFromUpdate(update) } func NewLightClientOptimisticUpdateFromBeaconState( ctx context.Context, + currentSlot primitives.Slot, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock, attestedState state.BeaconState, attestedBlock interfaces.ReadOnlySignedBeaconBlock, -) (*ethpbv2.LightClientOptimisticUpdate, error) { - update, err := NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, nil) +) (interfaces.LightClientOptimisticUpdate, error) { + update, err := NewLightClientUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock, nil) if err != nil { return nil, err } - return createLightClientOptimisticUpdate(update), nil + return light_client.NewOptimisticUpdateFromUpdate(update) } +// To form a LightClientUpdate, the following historical states and blocks are needed: +// - state: the post state of any block with a post-Altair parent block +// - block: the corresponding block +// - attested_state: the post state of attested_block +// - attested_block: the block referred to by block.parent_root +// - finalized_block: the block referred to by attested_state.finalized_checkpoint.root, +// if locally available (may be unavailable, e.g., when using checkpoint sync, or if it was pruned locally) func NewLightClientUpdateFromBeaconState( ctx context.Context, + currentSlot primitives.Slot, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock, attestedState state.BeaconState, attestedBlock interfaces.ReadOnlySignedBeaconBlock, - finalizedBlock interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientUpdate, error) { + finalizedBlock interfaces.ReadOnlySignedBeaconBlock) (interfaces.LightClientUpdate, error) { // assert compute_epoch_at_slot(attested_state.slot) >= ALTAIR_FORK_EPOCH attestedEpoch := slots.ToEpoch(attestedState.Slot()) if attestedEpoch < params.BeaconConfig().AltairForkEpoch { @@ -129,7 +116,11 @@ func NewLightClientUpdateFromBeaconState( // assert attested_state.slot == attested_state.latest_block_header.slot if attestedState.Slot() != attestedState.LatestBlockHeader().Slot { - return nil, fmt.Errorf("attested state slot %d not equal to attested latest block header slot %d", attestedState.Slot(), attestedState.LatestBlockHeader().Slot) + return nil, fmt.Errorf( + "attested state slot %d not equal to attested latest block header slot %d", + attestedState.Slot(), + attestedState.LatestBlockHeader().Slot, + ) } // attested_header = attested_state.latest_block_header.copy() @@ -153,46 +144,58 @@ func NewLightClientUpdateFromBeaconState( } // assert hash_tree_root(attested_header) == hash_tree_root(attested_block.message) == block.message.parent_root if attestedHeaderRoot != block.Block().ParentRoot() || attestedHeaderRoot != attestedBlockRoot { - return nil, fmt.Errorf("attested header root %#x not equal to block parent root %#x or attested block root %#x", attestedHeaderRoot, block.Block().ParentRoot(), attestedBlockRoot) + return nil, fmt.Errorf( + "attested header root %#x not equal to block parent root %#x or attested block root %#x", + attestedHeaderRoot, + block.Block().ParentRoot(), + attestedBlockRoot, + ) } // update_attested_period = compute_sync_committee_period_at_slot(attested_block.message.slot) updateAttestedPeriod := slots.SyncCommitteePeriod(slots.ToEpoch(attestedBlock.Block().Slot())) // update = LightClientUpdate() - result, err := createDefaultLightClientUpdate() + result, err := CreateDefaultLightClientUpdate(currentSlot, attestedState) if err != nil { return nil, errors.Wrap(err, "could not create default light client update") } // update.attested_header = block_to_light_client_header(attested_block) - attestedLightClientHeader, err := BlockToLightClientHeader(attestedBlock) + attestedLightClientHeader, err := BlockToLightClientHeader(ctx, currentSlot, attestedBlock) if err != nil { return nil, errors.Wrap(err, "could not get attested light client header") } - result.AttestedHeader = attestedLightClientHeader + if err = result.SetAttestedHeader(attestedLightClientHeader); err != nil { + return nil, errors.Wrap(err, "could not set attested header") + } // if update_attested_period == update_signature_period if updateAttestedPeriod == updateSignaturePeriod { + // update.next_sync_committee = attested_state.next_sync_committee tempNextSyncCommittee, err := attestedState.NextSyncCommittee() if err != nil { return nil, errors.Wrap(err, "could not get next sync committee") } - nextSyncCommittee := ðpbv2.SyncCommittee{ + nextSyncCommittee := &pb.SyncCommittee{ Pubkeys: tempNextSyncCommittee.Pubkeys, AggregatePubkey: tempNextSyncCommittee.AggregatePubkey, } + result.SetNextSyncCommittee(nextSyncCommittee) + + // update.next_sync_committee_branch = NextSyncCommitteeBranch( + // compute_merkle_proof(attested_state, next_sync_committee_gindex_at_slot(attested_state.slot))) nextSyncCommitteeBranch, err := attestedState.NextSyncCommitteeProof(ctx) if err != nil { return nil, errors.Wrap(err, "could not get next sync committee proof") } - - // update.next_sync_committee = attested_state.next_sync_committee - result.NextSyncCommittee = nextSyncCommittee - - // update.next_sync_committee_branch = NextSyncCommitteeBranch( - // compute_merkle_proof(attested_state, next_sync_committee_gindex_at_slot(attested_state.slot))) - result.NextSyncCommitteeBranch = nextSyncCommitteeBranch + if attestedBlock.Version() >= version.Electra { + if err = result.SetNextSyncCommitteeBranch(nextSyncCommitteeBranch); err != nil { + return nil, errors.Wrap(err, "could not set next sync committee branch") + } + } else if err = result.SetNextSyncCommitteeBranch(nextSyncCommitteeBranch); err != nil { + return nil, errors.Wrap(err, "could not set next sync committee branch") + } } // if finalized_block is not None @@ -200,11 +203,13 @@ func NewLightClientUpdateFromBeaconState( // if finalized_block.message.slot != GENESIS_SLOT if finalizedBlock.Block().Slot() != 0 { // update.finalized_header = block_to_light_client_header(finalized_block) - finalizedLightClientHeader, err := BlockToLightClientHeader(finalizedBlock) + finalizedLightClientHeader, err := BlockToLightClientHeader(ctx, currentSlot, finalizedBlock) if err != nil { return nil, errors.Wrap(err, "could not get finalized light client header") } - result.FinalizedHeader = finalizedLightClientHeader + if err = result.SetFinalizedHeader(finalizedLightClientHeader); err != nil { + return nil, errors.Wrap(err, "could not set finalized header") + } } else { // assert attested_state.finalized_checkpoint.root == Bytes32() if !bytes.Equal(attestedState.FinalizedCheckpoint().Root, make([]byte, 32)) { @@ -218,49 +223,120 @@ func NewLightClientUpdateFromBeaconState( if err != nil { return nil, errors.Wrap(err, "could not get finalized root proof") } - result.FinalityBranch = finalityBranch + if err = result.SetFinalityBranch(finalityBranch); err != nil { + return nil, errors.Wrap(err, "could not set finality branch") + } } // update.sync_aggregate = block.message.body.sync_aggregate - result.SyncAggregate = ðpbv1.SyncAggregate{ + result.SetSyncAggregate(&pb.SyncAggregate{ SyncCommitteeBits: syncAggregate.SyncCommitteeBits, SyncCommitteeSignature: syncAggregate.SyncCommitteeSignature, - } + }) // update.signature_slot = block.message.slot - result.SignatureSlot = block.Block().Slot() + result.SetSignatureSlot(block.Block().Slot()) return result, nil } -func createDefaultLightClientUpdate() (*ethpbv2.LightClientUpdate, error) { +func CreateDefaultLightClientUpdate(currentSlot primitives.Slot, attestedState state.BeaconState) (interfaces.LightClientUpdate, error) { + currentEpoch := slots.ToEpoch(currentSlot) + syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize pubKeys := make([][]byte, syncCommitteeSize) for i := uint64(0); i < syncCommitteeSize; i++ { pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength) } - nextSyncCommittee := ðpbv2.SyncCommittee{ + nextSyncCommittee := &pb.SyncCommittee{ Pubkeys: pubKeys, AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength), } - nextSyncCommitteeBranch := make([][]byte, fieldparams.SyncCommitteeBranchDepth) - for i := 0; i < fieldparams.SyncCommitteeBranchDepth; i++ { + + var nextSyncCommitteeBranch [][]byte + if attestedState.Version() >= version.Electra { + nextSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepthElectra) + } else { + nextSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepth) + } + for i := 0; i < len(nextSyncCommitteeBranch); i++ { nextSyncCommitteeBranch[i] = make([]byte, fieldparams.RootLength) } - executionBranch := make([][]byte, executionBranchNumOfLeaves) - for i := 0; i < executionBranchNumOfLeaves; i++ { + + executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth) + for i := 0; i < fieldparams.ExecutionBranchDepth; i++ { executionBranch[i] = make([]byte, 32) } - finalityBranch := make([][]byte, FinalityBranchNumOfLeaves) - for i := 0; i < FinalityBranchNumOfLeaves; i++ { + + var finalityBranch [][]byte + if attestedState.Version() >= version.Electra { + finalityBranch = make([][]byte, fieldparams.FinalityBranchDepthElectra) + } else { + finalityBranch = make([][]byte, fieldparams.FinalityBranchDepth) + } + for i := 0; i < len(finalityBranch); i++ { finalityBranch[i] = make([]byte, 32) } - return ðpbv2.LightClientUpdate{ - NextSyncCommittee: nextSyncCommittee, - NextSyncCommitteeBranch: nextSyncCommitteeBranch, - FinalityBranch: finalityBranch, - }, nil + var m proto.Message + if currentEpoch < params.BeaconConfig().CapellaForkEpoch { + m = &pb.LightClientUpdateAltair{ + AttestedHeader: &pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{}, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else if currentEpoch < params.BeaconConfig().DenebForkEpoch { + m = &pb.LightClientUpdateCapella{ + AttestedHeader: &pb.LightClientHeaderCapella{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderCapella{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else if currentEpoch < params.BeaconConfig().ElectraForkEpoch { + m = &pb.LightClientUpdateDeneb{ + AttestedHeader: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else { + if attestedState.Version() >= version.Electra { + m = &pb.LightClientUpdateElectra{ + AttestedHeader: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else { + m = &pb.LightClientUpdateDeneb{ + AttestedHeader: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } + } + + return light_client.NewWrappedUpdate(m) } func ComputeTransactionsRoot(payload interfaces.ExecutionData) ([]byte, error) { @@ -299,196 +375,446 @@ func ComputeWithdrawalsRoot(payload interfaces.ExecutionData) ([]byte, error) { return withdrawalsRoot, nil } -func BlockToLightClientHeader(block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderContainer, error) { - switch block.Version() { - case version.Altair, version.Bellatrix: - altairHeader, err := blockToLightClientHeaderAltair(block) - if err != nil { - return nil, errors.Wrap(err, "could not get header") - } - return ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: altairHeader, +func BlockToLightClientHeader( + ctx context.Context, + currentSlot primitives.Slot, + block interfaces.ReadOnlySignedBeaconBlock, +) (interfaces.LightClientHeader, error) { + var m proto.Message + currentEpoch := slots.ToEpoch(currentSlot) + blockEpoch := slots.ToEpoch(block.Block().Slot()) + parentRoot := block.Block().ParentRoot() + stateRoot := block.Block().StateRoot() + bodyRoot, err := block.Block().Body().HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not get body root") + } + + if currentEpoch < params.BeaconConfig().CapellaForkEpoch { + m = &pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: block.Block().Slot(), + ProposerIndex: block.Block().ProposerIndex(), + ParentRoot: parentRoot[:], + StateRoot: stateRoot[:], + BodyRoot: bodyRoot[:], }, - }, nil - case version.Capella: - capellaHeader, err := blockToLightClientHeaderCapella(context.Background(), block) - if err != nil { - return nil, errors.Wrap(err, "could not get capella header") } - return ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ - HeaderCapella: capellaHeader, + } else if currentEpoch < params.BeaconConfig().DenebForkEpoch { + var payloadHeader *enginev1.ExecutionPayloadHeaderCapella + var payloadProof [][]byte + + if blockEpoch < params.BeaconConfig().CapellaForkEpoch { + payloadHeader = &enginev1.ExecutionPayloadHeaderCapella{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + } + payloadProof = emptyPayloadProof() + } else { + payload, err := block.Block().Body().Execution() + if err != nil { + return nil, errors.Wrap(err, "could not get execution payload") + } + transactionsRoot, err := ComputeTransactionsRoot(payload) + if err != nil { + return nil, errors.Wrap(err, "could not get transactions root") + } + withdrawalsRoot, err := ComputeWithdrawalsRoot(payload) + if err != nil { + return nil, errors.Wrap(err, "could not get withdrawals root") + } + + payloadHeader = &enginev1.ExecutionPayloadHeaderCapella{ + ParentHash: payload.ParentHash(), + FeeRecipient: payload.FeeRecipient(), + StateRoot: payload.StateRoot(), + ReceiptsRoot: payload.ReceiptsRoot(), + LogsBloom: payload.LogsBloom(), + PrevRandao: payload.PrevRandao(), + BlockNumber: payload.BlockNumber(), + GasLimit: payload.GasLimit(), + GasUsed: payload.GasUsed(), + Timestamp: payload.Timestamp(), + ExtraData: payload.ExtraData(), + BaseFeePerGas: payload.BaseFeePerGas(), + BlockHash: payload.BlockHash(), + TransactionsRoot: transactionsRoot, + WithdrawalsRoot: withdrawalsRoot, + } + + payloadProof, err = blocks.PayloadProof(ctx, block.Block()) + if err != nil { + return nil, errors.Wrap(err, "could not get execution payload proof") + } + } + + m = &pb.LightClientHeaderCapella{ + Beacon: &pb.BeaconBlockHeader{ + Slot: block.Block().Slot(), + ProposerIndex: block.Block().ProposerIndex(), + ParentRoot: parentRoot[:], + StateRoot: stateRoot[:], + BodyRoot: bodyRoot[:], }, - }, nil - case version.Deneb, version.Electra: - denebHeader, err := blockToLightClientHeaderDeneb(context.Background(), block) - if err != nil { - return nil, errors.Wrap(err, "could not get header") + Execution: payloadHeader, + ExecutionBranch: payloadProof, } - return ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ - HeaderDeneb: denebHeader, + } else { + var payloadHeader *enginev1.ExecutionPayloadHeaderDeneb + var payloadProof [][]byte + + if blockEpoch < params.BeaconConfig().CapellaForkEpoch { + var ok bool + + p, err := execution.EmptyExecutionPayload(version.Deneb) + if err != nil { + return nil, errors.Wrap(err, "could not get payload header") + } + payloadHeader, ok = p.(*enginev1.ExecutionPayloadHeaderDeneb) + if !ok { + return nil, errors.Wrapf(err, "payload header type %T is not %T", payloadHeader, &enginev1.ExecutionPayloadHeaderDeneb{}) + } + payloadProof = emptyPayloadProof() + } else { + payload, err := block.Block().Body().Execution() + if err != nil { + return nil, errors.Wrap(err, "could not get execution payload") + } + transactionsRoot, err := ComputeTransactionsRoot(payload) + if err != nil { + return nil, errors.Wrap(err, "could not get transactions root") + } + withdrawalsRoot, err := ComputeWithdrawalsRoot(payload) + if err != nil { + return nil, errors.Wrap(err, "could not get withdrawals root") + } + + var blobGasUsed uint64 + var excessBlobGas uint64 + + if blockEpoch >= params.BeaconConfig().DenebForkEpoch { + blobGasUsed, err = payload.BlobGasUsed() + if err != nil { + return nil, errors.Wrap(err, "could not get blob gas used") + } + excessBlobGas, err = payload.ExcessBlobGas() + if err != nil { + return nil, errors.Wrap(err, "could not get excess blob gas") + } + } + + payloadHeader = &enginev1.ExecutionPayloadHeaderDeneb{ + ParentHash: payload.ParentHash(), + FeeRecipient: payload.FeeRecipient(), + StateRoot: payload.StateRoot(), + ReceiptsRoot: payload.ReceiptsRoot(), + LogsBloom: payload.LogsBloom(), + PrevRandao: payload.PrevRandao(), + BlockNumber: payload.BlockNumber(), + GasLimit: payload.GasLimit(), + GasUsed: payload.GasUsed(), + Timestamp: payload.Timestamp(), + ExtraData: payload.ExtraData(), + BaseFeePerGas: payload.BaseFeePerGas(), + BlockHash: payload.BlockHash(), + TransactionsRoot: transactionsRoot, + WithdrawalsRoot: withdrawalsRoot, + BlobGasUsed: blobGasUsed, + ExcessBlobGas: excessBlobGas, + } + + payloadProof, err = blocks.PayloadProof(ctx, block.Block()) + if err != nil { + return nil, errors.Wrap(err, "could not get execution payload proof") + } + } + + m = &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{ + Slot: block.Block().Slot(), + ProposerIndex: block.Block().ProposerIndex(), + ParentRoot: parentRoot[:], + StateRoot: stateRoot[:], + BodyRoot: bodyRoot[:], }, - }, nil - default: - return nil, fmt.Errorf("unsupported block version %s", version.String(block.Version())) + Execution: payloadHeader, + ExecutionBranch: payloadProof, + } } + + return light_client.NewWrappedHeader(m) } -func blockToLightClientHeaderAltair(block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeader, error) { - if block.Version() < version.Altair { - return nil, fmt.Errorf("block version is %s instead of Altair", version.String(block.Version())) +func emptyPayloadProof() [][]byte { + branch := interfaces.LightClientExecutionBranch{} + proof := make([][]byte, len(branch)) + for i, b := range branch { + proof[i] = b[:] } + return proof +} - parentRoot := block.Block().ParentRoot() - stateRoot := block.Block().StateRoot() - bodyRoot, err := block.Block().Body().HashTreeRoot() +func HasRelevantSyncCommittee(update interfaces.LightClientUpdate) (bool, error) { + if update.Version() >= version.Electra { + branch, err := update.NextSyncCommitteeBranchElectra() + if err != nil { + return false, err + } + return !reflect.DeepEqual(branch, interfaces.LightClientSyncCommitteeBranchElectra{}), nil + } + branch, err := update.NextSyncCommitteeBranch() if err != nil { - return nil, errors.Wrap(err, "could not get body root") + return false, err } - - return ðpbv2.LightClientHeader{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: block.Block().Slot(), - ProposerIndex: block.Block().ProposerIndex(), - ParentRoot: parentRoot[:], - StateRoot: stateRoot[:], - BodyRoot: bodyRoot[:], - }, - }, nil + return !reflect.DeepEqual(branch, interfaces.LightClientSyncCommitteeBranch{}), nil } -func blockToLightClientHeaderCapella(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderCapella, error) { - if block.Version() < version.Capella { - return nil, fmt.Errorf("block version is %s instead of Capella", version.String(block.Version())) +func HasFinality(update interfaces.LightClientUpdate) (bool, error) { + if update.Version() >= version.Electra { + b, err := update.FinalityBranchElectra() + if err != nil { + return false, err + } + return !reflect.DeepEqual(b, interfaces.LightClientFinalityBranchElectra{}), nil } - payload, err := block.Block().Body().Execution() + b, err := update.FinalityBranch() if err != nil { - return nil, errors.Wrap(err, "could not get execution payload") + return false, err + } + return !reflect.DeepEqual(b, interfaces.LightClientFinalityBranch{}), nil +} + +func IsBetterUpdate(newUpdate, oldUpdate interfaces.LightClientUpdate) (bool, error) { + maxActiveParticipants := newUpdate.SyncAggregate().SyncCommitteeBits.Len() + newNumActiveParticipants := newUpdate.SyncAggregate().SyncCommitteeBits.Count() + oldNumActiveParticipants := oldUpdate.SyncAggregate().SyncCommitteeBits.Count() + newHasSupermajority := newNumActiveParticipants*3 >= maxActiveParticipants*2 + oldHasSupermajority := oldNumActiveParticipants*3 >= maxActiveParticipants*2 + + if newHasSupermajority != oldHasSupermajority { + return newHasSupermajority, nil } + if !newHasSupermajority && newNumActiveParticipants != oldNumActiveParticipants { + return newNumActiveParticipants > oldNumActiveParticipants, nil + } + + newUpdateAttestedHeaderBeacon := newUpdate.AttestedHeader().Beacon() + oldUpdateAttestedHeaderBeacon := oldUpdate.AttestedHeader().Beacon() - transactionsRoot, err := ComputeTransactionsRoot(payload) + // Compare presence of relevant sync committee + newHasRelevantSyncCommittee, err := HasRelevantSyncCommittee(newUpdate) if err != nil { - return nil, err + return false, err } - withdrawalsRoot, err := ComputeWithdrawalsRoot(payload) + newHasRelevantSyncCommittee = newHasRelevantSyncCommittee && + (slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.SignatureSlot()))) + oldHasRelevantSyncCommittee, err := HasRelevantSyncCommittee(oldUpdate) if err != nil { - return nil, err + return false, err } + oldHasRelevantSyncCommittee = oldHasRelevantSyncCommittee && + (slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.SignatureSlot()))) - executionHeader := &v11.ExecutionPayloadHeaderCapella{ - ParentHash: payload.ParentHash(), - FeeRecipient: payload.FeeRecipient(), - StateRoot: payload.StateRoot(), - ReceiptsRoot: payload.ReceiptsRoot(), - LogsBloom: payload.LogsBloom(), - PrevRandao: payload.PrevRandao(), - BlockNumber: payload.BlockNumber(), - GasLimit: payload.GasLimit(), - GasUsed: payload.GasUsed(), - Timestamp: payload.Timestamp(), - ExtraData: payload.ExtraData(), - BaseFeePerGas: payload.BaseFeePerGas(), - BlockHash: payload.BlockHash(), - TransactionsRoot: transactionsRoot, - WithdrawalsRoot: withdrawalsRoot, - } - - executionPayloadProof, err := blocks.PayloadProof(ctx, block.Block()) - if err != nil { - return nil, errors.Wrap(err, "could not get execution payload proof") + if newHasRelevantSyncCommittee != oldHasRelevantSyncCommittee { + return newHasRelevantSyncCommittee, nil } - parentRoot := block.Block().ParentRoot() - stateRoot := block.Block().StateRoot() - bodyRoot, err := block.Block().Body().HashTreeRoot() + // Compare indication of any finality + newHasFinality, err := HasFinality(newUpdate) if err != nil { - return nil, errors.Wrap(err, "could not get body root") + return false, err + } + oldHasFinality, err := HasFinality(oldUpdate) + if err != nil { + return false, err + } + if newHasFinality != oldHasFinality { + return newHasFinality, nil + } + + newUpdateFinalizedHeaderBeacon := newUpdate.FinalizedHeader().Beacon() + oldUpdateFinalizedHeaderBeacon := oldUpdate.FinalizedHeader().Beacon() + + // Compare sync committee finality + if newHasFinality { + newHasSyncCommitteeFinality := + slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateFinalizedHeaderBeacon.Slot)) == + slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateAttestedHeaderBeacon.Slot)) + oldHasSyncCommitteeFinality := + slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateFinalizedHeaderBeacon.Slot)) == + slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateAttestedHeaderBeacon.Slot)) + + if newHasSyncCommitteeFinality != oldHasSyncCommitteeFinality { + return newHasSyncCommitteeFinality, nil + } + } + + // Tiebreaker 1: Sync committee participation beyond supermajority + if newNumActiveParticipants != oldNumActiveParticipants { + return newNumActiveParticipants > oldNumActiveParticipants, nil + } + + // Tiebreaker 2: Prefer older data (fewer changes to best) + if newUpdateAttestedHeaderBeacon.Slot != oldUpdateAttestedHeaderBeacon.Slot { + return newUpdateAttestedHeaderBeacon.Slot < oldUpdateAttestedHeaderBeacon.Slot, nil } - return ðpbv2.LightClientHeaderCapella{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: block.Block().Slot(), - ProposerIndex: block.Block().ProposerIndex(), - ParentRoot: parentRoot[:], - StateRoot: stateRoot[:], - BodyRoot: bodyRoot[:], - }, - Execution: executionHeader, - ExecutionBranch: executionPayloadProof, - }, nil + return newUpdate.SignatureSlot() < oldUpdate.SignatureSlot(), nil } -func blockToLightClientHeaderDeneb(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderDeneb, error) { - if block.Version() < version.Deneb { - return nil, fmt.Errorf("block version is %s instead of Deneb/Electra", version.String(block.Version())) +func NewLightClientBootstrapFromBeaconState( + ctx context.Context, + currentSlot primitives.Slot, + state state.BeaconState, + block interfaces.ReadOnlySignedBeaconBlock, +) (interfaces.LightClientBootstrap, error) { + // assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH + if slots.ToEpoch(state.Slot()) < params.BeaconConfig().AltairForkEpoch { + return nil, fmt.Errorf("light client bootstrap is not supported before Altair, invalid slot %d", state.Slot()) + } + + // assert state.slot == state.latest_block_header.slot + latestBlockHeader := state.LatestBlockHeader() + if state.Slot() != latestBlockHeader.Slot { + return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot) } - payload, err := block.Block().Body().Execution() + // header.state_root = hash_tree_root(state) + stateRoot, err := state.HashTreeRoot(ctx) if err != nil { - return nil, errors.Wrap(err, "could not get execution payload") + return nil, errors.Wrap(err, "could not get state root") } + latestBlockHeader.StateRoot = stateRoot[:] - transactionsRoot, err := ComputeTransactionsRoot(payload) + // assert hash_tree_root(header) == hash_tree_root(block.message) + latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot() if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not get latest block header root") } - withdrawalsRoot, err := ComputeWithdrawalsRoot(payload) + beaconBlockRoot, err := block.Block().HashTreeRoot() if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not get block root") + } + if latestBlockHeaderRoot != beaconBlockRoot { + return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot) } - blobGasUsed, err := payload.BlobGasUsed() + + bootstrap, err := createDefaultLightClientBootstrap(currentSlot) if err != nil { - return nil, errors.Wrap(err, "could not get blob gas used") + return nil, errors.Wrap(err, "could not create default light client bootstrap") } - excessBlobGas, err := payload.ExcessBlobGas() + + lightClientHeader, err := BlockToLightClientHeader(ctx, currentSlot, block) if err != nil { - return nil, errors.Wrap(err, "could not get excess blob gas") - } - - executionHeader := &v11.ExecutionPayloadHeaderDeneb{ - ParentHash: payload.ParentHash(), - FeeRecipient: payload.FeeRecipient(), - StateRoot: payload.StateRoot(), - ReceiptsRoot: payload.ReceiptsRoot(), - LogsBloom: payload.LogsBloom(), - PrevRandao: payload.PrevRandao(), - BlockNumber: payload.BlockNumber(), - GasLimit: payload.GasLimit(), - GasUsed: payload.GasUsed(), - Timestamp: payload.Timestamp(), - ExtraData: payload.ExtraData(), - BaseFeePerGas: payload.BaseFeePerGas(), - BlockHash: payload.BlockHash(), - TransactionsRoot: transactionsRoot, - WithdrawalsRoot: withdrawalsRoot, - BlobGasUsed: blobGasUsed, - ExcessBlobGas: excessBlobGas, - } - - executionPayloadProof, err := blocks.PayloadProof(ctx, block.Block()) + return nil, errors.Wrap(err, "could not convert block to light client header") + } + + err = bootstrap.SetHeader(lightClientHeader) if err != nil { - return nil, errors.Wrap(err, "could not get execution payload proof") + return nil, errors.Wrap(err, "could not set header") } - parentRoot := block.Block().ParentRoot() - stateRoot := block.Block().StateRoot() - bodyRoot, err := block.Block().Body().HashTreeRoot() + currentSyncCommittee, err := state.CurrentSyncCommittee() if err != nil { - return nil, errors.Wrap(err, "could not get body root") + return nil, errors.Wrap(err, "could not get current sync committee") + } + + err = bootstrap.SetCurrentSyncCommittee(currentSyncCommittee) + if err != nil { + return nil, errors.Wrap(err, "could not set current sync committee") + } + + currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx) + if err != nil { + return nil, errors.Wrap(err, "could not get current sync committee proof") + } + + err = bootstrap.SetCurrentSyncCommitteeBranch(currentSyncCommitteeProof) + if err != nil { + return nil, errors.Wrap(err, "could not set current sync committee proof") + } + + return bootstrap, nil +} + +func createDefaultLightClientBootstrap(currentSlot primitives.Slot) (interfaces.LightClientBootstrap, error) { + currentEpoch := slots.ToEpoch(currentSlot) + syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize + pubKeys := make([][]byte, syncCommitteeSize) + for i := uint64(0); i < syncCommitteeSize; i++ { + pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength) + } + currentSyncCommittee := &pb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength), + } + + var currentSyncCommitteeBranch [][]byte + if currentEpoch >= params.BeaconConfig().ElectraForkEpoch { + currentSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepthElectra) + } else { + currentSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepth) + } + for i := 0; i < len(currentSyncCommitteeBranch); i++ { + currentSyncCommitteeBranch[i] = make([]byte, fieldparams.RootLength) + } + + executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth) + for i := 0; i < fieldparams.ExecutionBranchDepth; i++ { + executionBranch[i] = make([]byte, 32) + } + + // TODO: can this be based on the current epoch? + var m proto.Message + if currentEpoch < params.BeaconConfig().CapellaForkEpoch { + m = &pb.LightClientBootstrapAltair{ + Header: &pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{}, + }, + CurrentSyncCommittee: currentSyncCommittee, + CurrentSyncCommitteeBranch: currentSyncCommitteeBranch, + } + } else if currentEpoch < params.BeaconConfig().DenebForkEpoch { + m = &pb.LightClientBootstrapCapella{ + Header: &pb.LightClientHeaderCapella{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderCapella{}, + ExecutionBranch: executionBranch, + }, + CurrentSyncCommittee: currentSyncCommittee, + CurrentSyncCommitteeBranch: currentSyncCommitteeBranch, + } + } else if currentEpoch < params.BeaconConfig().ElectraForkEpoch { + m = &pb.LightClientBootstrapDeneb{ + Header: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + CurrentSyncCommittee: currentSyncCommittee, + CurrentSyncCommitteeBranch: currentSyncCommitteeBranch, + } + } else { + m = &pb.LightClientBootstrapElectra{ + Header: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + CurrentSyncCommittee: currentSyncCommittee, + CurrentSyncCommitteeBranch: currentSyncCommitteeBranch, + } } - return ðpbv2.LightClientHeaderDeneb{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: block.Block().Slot(), - ProposerIndex: block.Block().ProposerIndex(), - ParentRoot: parentRoot[:], - StateRoot: stateRoot[:], - BodyRoot: bodyRoot[:], - }, - Execution: executionHeader, - ExecutionBranch: executionPayloadProof, - }, nil + return light_client.NewWrappedBootstrap(m) } diff --git a/beacon-chain/core/light-client/lightclient_test.go b/beacon-chain/core/light-client/lightclient_test.go index eb6ab4417083..e26a4d2f7daf 100644 --- a/beacon-chain/core/light-client/lightclient_test.go +++ b/beacon-chain/core/light-client/lightclient_test.go @@ -1,16 +1,17 @@ package light_client_test import ( + "reflect" "testing" "github.com/pkg/errors" + lightClient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" consensustypes "github.com/prysmaticlabs/prysm/v5/consensus-types" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/encoding/ssz" v11 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" - - lightClient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" + pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" ) @@ -19,39 +20,39 @@ func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T) t.Run("Altair", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestAltair() - update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock) + update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) }) t.Run("Capella", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestCapella(false) - update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock) + update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) }) t.Run("Deneb", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestDeneb(false) - update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock) + update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) }) } @@ -60,33 +61,33 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) { l := util.NewTestLightClient(t).SetupTestAltair() t.Run("FinalizedBlock Not Nil", func(t *testing.T) { - update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) + update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) finalizedBlockHeader, err := l.FinalizedBlock.Header() require.NoError(t, err) //zeroHash := params.BeaconConfig().ZeroHash[:] - require.NotNil(t, update.FinalizedHeader, "Finalized header is nil") - updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon() - require.NoError(t, err) + require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil") + require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderAltair{}), "Finalized header is not Altair") + updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon() require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal") require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal") - require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves") - - finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx) + fb, err := update.FinalityBranch() require.NoError(t, err) - for i, leaf := range update.FinalityBranch { - require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal") + proof, err := l.AttestedState.FinalizedRootProof(l.Ctx) + require.NoError(t, err) + for i, leaf := range fb { + require.DeepSSZEqual(t, proof[i], leaf[:], "Leaf is not equal") } }) }) @@ -95,30 +96,31 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) { t.Run("FinalizedBlock Not Nil", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestCapella(false) - update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) + update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) finalizedBlockHeader, err := l.FinalizedBlock.Header() require.NoError(t, err) - require.NotNil(t, update.FinalizedHeader, "Finalized header is nil") - updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon() - require.NoError(t, err) + require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil") + require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderCapella{}), "Finalized header is not Capella") + updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon() require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal") require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal") - require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves") - finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx) + fb, err := update.FinalityBranch() require.NoError(t, err) - for i, leaf := range update.FinalityBranch { - require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal") + proof, err := l.AttestedState.FinalizedRootProof(l.Ctx) + require.NoError(t, err) + for i, leaf := range fb { + require.DeepSSZEqual(t, proof[i], leaf[:], "Leaf is not equal") } // Check Execution BlockHash @@ -161,35 +163,38 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) { TransactionsRoot: transactionsRoot, WithdrawalsRoot: withdrawalsRoot, } - require.DeepSSZEqual(t, execution, update.FinalizedHeader.GetHeaderCapella().Execution, "Finalized Block Execution is not equal") + updateExecution, err := update.FinalizedHeader().Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, execution, updateExecution.Proto(), "Finalized Block Execution is not equal") }) t.Run("FinalizedBlock In Previous Fork", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestCapellaFinalizedBlockAltair(false) - update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) + update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) finalizedBlockHeader, err := l.FinalizedBlock.Header() require.NoError(t, err) - require.NotNil(t, update.FinalizedHeader, "Finalized header is nil") - updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon() - require.NoError(t, err) + require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil") + require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderCapella{}), "Finalized header is not Capella") + updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon() require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal") require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal") - require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves") - finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx) + fb, err := update.FinalityBranch() + require.NoError(t, err) + proof, err := l.AttestedState.FinalizedRootProof(l.Ctx) require.NoError(t, err) - for i, leaf := range update.FinalityBranch { - require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal") + for i, leaf := range fb { + require.DeepSSZEqual(t, proof[i], leaf[:], "Leaf is not equal") } }) }) @@ -199,31 +204,31 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) { t.Run("FinalizedBlock Not Nil", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestDeneb(false) - update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) + update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) //zeroHash := params.BeaconConfig().ZeroHash[:] finalizedBlockHeader, err := l.FinalizedBlock.Header() require.NoError(t, err) - require.NotNil(t, update.FinalizedHeader, "Finalized header is nil") - updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon() - require.NoError(t, err) + require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil") + updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon() require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal") require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal") - require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves") - finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx) + fb, err := update.FinalityBranch() + require.NoError(t, err) + proof, err := l.AttestedState.FinalizedRootProof(l.Ctx) require.NoError(t, err) - for i, leaf := range update.FinalityBranch { - require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal") + for i, leaf := range fb { + require.DeepSSZEqual(t, proof[i], leaf[:], "Leaf is not equal") } // Check Execution BlockHash @@ -266,36 +271,39 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) { TransactionsRoot: transactionsRoot, WithdrawalsRoot: withdrawalsRoot, } - require.DeepSSZEqual(t, execution, update.FinalizedHeader.GetHeaderDeneb().Execution, "Finalized Block Execution is not equal") + updateExecution, err := update.FinalizedHeader().Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, execution, updateExecution.Proto(), "Finalized Block Execution is not equal") }) t.Run("FinalizedBlock In Previous Fork", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestDenebFinalizedBlockCapella(false) - update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) + update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock) require.NoError(t, err) require.NotNil(t, update, "update is nil") - require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal") + require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal") - l.CheckSyncAggregate(update.SyncAggregate) - l.CheckAttestedHeader(update.AttestedHeader) + l.CheckSyncAggregate(update.SyncAggregate()) + l.CheckAttestedHeader(update.AttestedHeader()) finalizedBlockHeader, err := l.FinalizedBlock.Header() require.NoError(t, err) - require.NotNil(t, update.FinalizedHeader, "Finalized header is nil") - updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon() - require.NoError(t, err) + require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil") + updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon() + require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderDeneb{}), "Finalized header is not Deneb") require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal") require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal") require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal") - require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves") - finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx) + fb, err := update.FinalityBranch() require.NoError(t, err) - for i, leaf := range update.FinalityBranch { - require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal") + proof, err := l.AttestedState.FinalizedRootProof(l.Ctx) + require.NoError(t, err) + for i, leaf := range fb { + require.DeepSSZEqual(t, proof[i], leaf[:], "Leaf is not equal") } // Check Execution BlockHash @@ -321,7 +329,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) { } else { require.NoError(t, err) } - execution := &v11.ExecutionPayloadHeaderCapella{ + execution := &v11.ExecutionPayloadHeaderDeneb{ ParentHash: payloadInterface.ParentHash(), FeeRecipient: payloadInterface.FeeRecipient(), StateRoot: payloadInterface.StateRoot(), @@ -338,7 +346,9 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) { TransactionsRoot: transactionsRoot, WithdrawalsRoot: withdrawalsRoot, } - require.DeepSSZEqual(t, execution, update.FinalizedHeader.GetHeaderCapella().Execution, "Finalized Block Execution is not equal") + updateExecution, err := update.FinalizedHeader().Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, execution, updateExecution.Proto(), "Finalized Block Execution is not equal") }) }) } @@ -347,9 +357,8 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Altair", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestAltair() - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderAltair() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -357,19 +366,18 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { bodyRoot, err := l.Block.Block().Body().HashTreeRoot() require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") }) t.Run("Bellatrix", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestBellatrix() - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderAltair() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -377,20 +385,19 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { bodyRoot, err := l.Block.Block().Body().HashTreeRoot() require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") }) t.Run("Capella", func(t *testing.T) { t.Run("Non-Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestCapella(false) - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderCapella() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -428,23 +435,26 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") - require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal") + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") - require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal") + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") }) t.Run("Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestCapella(true) - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderCapella() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -482,15 +492,19 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") - require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal") + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") - require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal") + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") }) }) @@ -498,9 +512,8 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Non-Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestDeneb(false) - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderDeneb() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -546,23 +559,26 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") - require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal") + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") - require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal") + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") }) t.Run("Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestDeneb(true) - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderDeneb() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -608,15 +624,19 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") - require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal") + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") - require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal") + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") }) }) @@ -624,9 +644,8 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { t.Run("Non-Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestElectra(false) - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderDeneb() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -672,23 +691,26 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") - require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal") + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") - require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal") + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") }) t.Run("Blinded Beacon Block", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestElectra(true) - container, err := lightClient.BlockToLightClientHeader(l.Block) + header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block) require.NoError(t, err) - header := container.GetHeaderDeneb() require.NotNil(t, header, "header is nil") parentRoot := l.Block.Block().ParentRoot() @@ -734,15 +756,27 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) { executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block()) require.NoError(t, err) - require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal") - require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal") - require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal") - require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal") - require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal") + require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal") + require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal") + require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal") + require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal") + require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal") - require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal") + headerExecution, err := header.Execution() + require.NoError(t, err) + require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal") - require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal") + headerExecutionBranch, err := header.ExecutionBranch() + require.NoError(t, err) + require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal") }) }) } + +func convertArrayToSlice(arr [4][32]uint8) [][]uint8 { + slice := make([][]uint8, len(arr)) + for i := range arr { + slice[i] = arr[i][:] + } + return slice +} diff --git a/beacon-chain/db/iface/BUILD.bazel b/beacon-chain/db/iface/BUILD.bazel index 993d1fd84c98..81929a26a47d 100644 --- a/beacon-chain/db/iface/BUILD.bazel +++ b/beacon-chain/db/iface/BUILD.bazel @@ -18,7 +18,6 @@ go_library( "//consensus-types/primitives:go_default_library", "//monitoring/backup:go_default_library", "//proto/dbval:go_default_library", - "//proto/eth/v2:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", ], diff --git a/beacon-chain/db/iface/interface.go b/beacon-chain/db/iface/interface.go index b75960ef553c..082a3816b9b6 100644 --- a/beacon-chain/db/iface/interface.go +++ b/beacon-chain/db/iface/interface.go @@ -7,8 +7,6 @@ import ( "context" "io" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" - "github.com/ethereum/go-ethereum/common" "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filters" slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types" @@ -59,8 +57,9 @@ type ReadOnlyDatabase interface { FeeRecipientByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (common.Address, error) RegistrationByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error) // light client operations - LightClientUpdates(ctx context.Context, startPeriod, endPeriod uint64) (map[uint64]*ethpbv2.LightClientUpdateWithVersion, error) - LightClientUpdate(ctx context.Context, period uint64) (*ethpbv2.LightClientUpdateWithVersion, error) + LightClientUpdates(ctx context.Context, startPeriod, endPeriod uint64) (map[uint64]interfaces.LightClientUpdate, error) + LightClientUpdate(ctx context.Context, period uint64) (interfaces.LightClientUpdate, error) + LightClientBootstrap(ctx context.Context, blockRoot []byte) (interfaces.LightClientBootstrap, error) // origin checkpoint sync support OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error) @@ -98,7 +97,8 @@ type NoHeadAccessDatabase interface { SaveFeeRecipientsByValidatorIDs(ctx context.Context, ids []primitives.ValidatorIndex, addrs []common.Address) error SaveRegistrationsByValidatorIDs(ctx context.Context, ids []primitives.ValidatorIndex, regs []*ethpb.ValidatorRegistrationV1) error // light client operations - SaveLightClientUpdate(ctx context.Context, period uint64, update *ethpbv2.LightClientUpdateWithVersion) error + SaveLightClientUpdate(ctx context.Context, period uint64, update interfaces.LightClientUpdate) error + SaveLightClientBootstrap(ctx context.Context, blockRoot []byte, bootstrap interfaces.LightClientBootstrap) error CleanUpDirtyStates(ctx context.Context, slotsPerArchivedPoint primitives.Slot) error } diff --git a/beacon-chain/db/kv/BUILD.bazel b/beacon-chain/db/kv/BUILD.bazel index 732da2fb5b2a..714bfb1f26d4 100644 --- a/beacon-chain/db/kv/BUILD.bazel +++ b/beacon-chain/db/kv/BUILD.bazel @@ -44,6 +44,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", "//container/slice:go_default_library", "//encoding/bytesutil:go_default_library", @@ -53,7 +54,6 @@ go_library( "//monitoring/tracing:go_default_library", "//monitoring/tracing/trace:go_default_library", "//proto/dbval:go_default_library", - "//proto/eth/v2:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//runtime/version:go_default_library", "//time:go_default_library", @@ -112,18 +112,18 @@ go_test( "//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", "//encoding/bytesutil:go_default_library", "//proto/dbval:go_default_library", "//proto/engine/v1:go_default_library", - "//proto/eth/v1:go_default_library", - "//proto/eth/v2:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//proto/testing:go_default_library", "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_golang_snappy//:go_default_library", "@com_github_pkg_errors//:go_default_library", diff --git a/beacon-chain/db/kv/kv.go b/beacon-chain/db/kv/kv.go index 63e49e30485d..128b17eddf24 100644 --- a/beacon-chain/db/kv/kv.go +++ b/beacon-chain/db/kv/kv.go @@ -108,6 +108,7 @@ var Buckets = [][]byte{ stateSummaryBucket, stateValidatorsBucket, lightClientUpdatesBucket, + lightClientBootstrapBucket, // Indices buckets. blockSlotIndicesBucket, stateSlotIndicesBucket, diff --git a/beacon-chain/db/kv/lightclient.go b/beacon-chain/db/kv/lightclient.go index 3c7bef3ff5f1..2e3c8dc9c417 100644 --- a/beacon-chain/db/kv/lightclient.go +++ b/beacon-chain/db/kv/lightclient.go @@ -5,35 +5,126 @@ import ( "encoding/binary" "fmt" + "github.com/golang/snappy" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + light_client "github.com/prysmaticlabs/prysm/v5/consensus-types/light-client" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" bolt "go.etcd.io/bbolt" + "google.golang.org/protobuf/proto" ) -func (s *Store) SaveLightClientUpdate(ctx context.Context, period uint64, update *ethpbv2.LightClientUpdateWithVersion) error { - ctx, span := trace.StartSpan(ctx, "BeaconDB.saveLightClientUpdate") +func (s *Store) SaveLightClientUpdate(ctx context.Context, period uint64, update interfaces.LightClientUpdate) error { + _, span := trace.StartSpan(ctx, "BeaconDB.SaveLightClientUpdate") defer span.End() return s.db.Update(func(tx *bolt.Tx) error { bkt := tx.Bucket(lightClientUpdatesBucket) - updateMarshalled, err := encode(ctx, update) + enc, err := encodeLightClientUpdate(update) if err != nil { return err } - return bkt.Put(bytesutil.Uint64ToBytesBigEndian(period), updateMarshalled) + return bkt.Put(bytesutil.Uint64ToBytesBigEndian(period), enc) }) } -func (s *Store) LightClientUpdates(ctx context.Context, startPeriod, endPeriod uint64) (map[uint64]*ethpbv2.LightClientUpdateWithVersion, error) { - ctx, span := trace.StartSpan(ctx, "BeaconDB.LightClientUpdates") +func (s *Store) SaveLightClientBootstrap(ctx context.Context, blockRoot []byte, bootstrap interfaces.LightClientBootstrap) error { + _, span := trace.StartSpan(ctx, "BeaconDB.SaveLightClientBootstrap") + defer span.End() + + return s.db.Update(func(tx *bolt.Tx) error { + bkt := tx.Bucket(lightClientBootstrapBucket) + enc, err := encodeLightClientBootstrap(bootstrap) + if err != nil { + return err + } + return bkt.Put(blockRoot, enc) + }) +} + +func (s *Store) LightClientBootstrap(ctx context.Context, blockRoot []byte) (interfaces.LightClientBootstrap, error) { + _, span := trace.StartSpan(ctx, "BeaconDB.LightClientBootstrap") + defer span.End() + + var bootstrap interfaces.LightClientBootstrap + err := s.db.View(func(tx *bolt.Tx) error { + bkt := tx.Bucket(lightClientBootstrapBucket) + enc := bkt.Get(blockRoot) + if enc == nil { + return nil + } + var err error + bootstrap, err = decodeLightClientBootstrap(enc) + return err + }) + return bootstrap, err +} + +func encodeLightClientBootstrap(bootstrap interfaces.LightClientBootstrap) ([]byte, error) { + key, err := keyForLightClientUpdate(bootstrap.Version()) + if err != nil { + return nil, err + } + enc, err := bootstrap.MarshalSSZ() + if err != nil { + return nil, errors.Wrap(err, "could not marshal light client bootstrap") + } + fullEnc := make([]byte, len(key)+len(enc)) + copy(fullEnc, key) + copy(fullEnc[len(key):], enc) + return snappy.Encode(nil, fullEnc), nil +} + +func decodeLightClientBootstrap(enc []byte) (interfaces.LightClientBootstrap, error) { + var err error + enc, err = snappy.Decode(nil, enc) + if err != nil { + return nil, errors.Wrap(err, "could not snappy decode light client bootstrap") + } + var m proto.Message + switch { + case hasAltairKey(enc): + bootstrap := ðpb.LightClientBootstrapAltair{} + if err := bootstrap.UnmarshalSSZ(enc[len(altairKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Altair light client bootstrap") + } + m = bootstrap + case hasCapellaKey(enc): + bootstrap := ðpb.LightClientBootstrapCapella{} + if err := bootstrap.UnmarshalSSZ(enc[len(capellaKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Capella light client bootstrap") + } + m = bootstrap + case hasDenebKey(enc): + bootstrap := ðpb.LightClientBootstrapDeneb{} + if err := bootstrap.UnmarshalSSZ(enc[len(denebKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Deneb light client bootstrap") + } + m = bootstrap + case hasElectraKey(enc): + bootstrap := ðpb.LightClientBootstrapElectra{} + if err := bootstrap.UnmarshalSSZ(enc[len(electraKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Electra light client bootstrap") + } + m = bootstrap + default: + return nil, errors.New("decoding of saved light client bootstrap is unsupported") + } + return light_client.NewWrappedBootstrap(m) +} + +func (s *Store) LightClientUpdates(ctx context.Context, startPeriod, endPeriod uint64) (map[uint64]interfaces.LightClientUpdate, error) { + _, span := trace.StartSpan(ctx, "BeaconDB.LightClientUpdates") defer span.End() if startPeriod > endPeriod { return nil, fmt.Errorf("start period %d is greater than end period %d", startPeriod, endPeriod) } - updates := make(map[uint64]*ethpbv2.LightClientUpdateWithVersion) + updates := make(map[uint64]interfaces.LightClientUpdate) err := s.db.View(func(tx *bolt.Tx) error { bkt := tx.Bucket(lightClientUpdatesBucket) c := bkt.Cursor() @@ -46,11 +137,11 @@ func (s *Store) LightClientUpdates(ctx context.Context, startPeriod, endPeriod u for k, v := c.Seek(bytesutil.Uint64ToBytesBigEndian(startPeriod)); k != nil && binary.BigEndian.Uint64(k) <= endPeriod; k, v = c.Next() { currentPeriod := binary.BigEndian.Uint64(k) - var update ethpbv2.LightClientUpdateWithVersion - if err := decode(ctx, v, &update); err != nil { + update, err := decodeLightClientUpdate(v) + if err != nil { return err } - updates[currentPeriod] = &update + updates[currentPeriod] = update } return nil @@ -62,18 +153,88 @@ func (s *Store) LightClientUpdates(ctx context.Context, startPeriod, endPeriod u return updates, err } -func (s *Store) LightClientUpdate(ctx context.Context, period uint64) (*ethpbv2.LightClientUpdateWithVersion, error) { - ctx, span := trace.StartSpan(ctx, "BeaconDB.LightClientUpdate") +func (s *Store) LightClientUpdate(ctx context.Context, period uint64) (interfaces.LightClientUpdate, error) { + _, span := trace.StartSpan(ctx, "BeaconDB.LightClientUpdate") defer span.End() - var update ethpbv2.LightClientUpdateWithVersion + var update interfaces.LightClientUpdate err := s.db.View(func(tx *bolt.Tx) error { bkt := tx.Bucket(lightClientUpdatesBucket) updateBytes := bkt.Get(bytesutil.Uint64ToBytesBigEndian(period)) if updateBytes == nil { return nil } - return decode(ctx, updateBytes, &update) + var err error + update, err = decodeLightClientUpdate(updateBytes) + return err }) - return &update, err + return update, err +} + +func encodeLightClientUpdate(update interfaces.LightClientUpdate) ([]byte, error) { + key, err := keyForLightClientUpdate(update.Version()) + if err != nil { + return nil, err + } + enc, err := update.MarshalSSZ() + if err != nil { + return nil, errors.Wrap(err, "could not marshal light client update") + } + fullEnc := make([]byte, len(key)+len(enc)) + copy(fullEnc, key) + copy(fullEnc[len(key):], enc) + return snappy.Encode(nil, fullEnc), nil +} + +func decodeLightClientUpdate(enc []byte) (interfaces.LightClientUpdate, error) { + var err error + enc, err = snappy.Decode(nil, enc) + if err != nil { + return nil, errors.Wrap(err, "could not snappy decode light client update") + } + var m proto.Message + switch { + case hasAltairKey(enc): + update := ðpb.LightClientUpdateAltair{} + if err := update.UnmarshalSSZ(enc[len(altairKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Altair light client update") + } + m = update + case hasCapellaKey(enc): + update := ðpb.LightClientUpdateCapella{} + if err := update.UnmarshalSSZ(enc[len(capellaKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Capella light client update") + } + m = update + case hasDenebKey(enc): + update := ðpb.LightClientUpdateDeneb{} + if err := update.UnmarshalSSZ(enc[len(denebKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Deneb light client update") + } + m = update + case hasElectraKey(enc): + update := ðpb.LightClientUpdateElectra{} + if err := update.UnmarshalSSZ(enc[len(electraKey):]); err != nil { + return nil, errors.Wrap(err, "could not unmarshal Electra light client update") + } + m = update + default: + return nil, errors.New("decoding of saved light client update is unsupported") + } + return light_client.NewWrappedUpdate(m) +} + +func keyForLightClientUpdate(v int) ([]byte, error) { + switch v { + case version.Electra: + return electraKey, nil + case version.Deneb: + return denebKey, nil + case version.Capella: + return capellaKey, nil + case version.Altair: + return altairKey, nil + default: + return nil, fmt.Errorf("unsupported light client update version %s", version.String(v)) + } } diff --git a/beacon-chain/db/kv/lightclient_test.go b/beacon-chain/db/kv/lightclient_test.go index dd2df2acac8b..84702567a379 100644 --- a/beacon-chain/db/kv/lightclient_test.go +++ b/beacon-chain/db/kv/lightclient_test.go @@ -2,221 +2,231 @@ package kv import ( "context" + "fmt" + "math/rand" "testing" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + "github.com/prysmaticlabs/prysm/v5/config/params" + "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" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" - ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" + pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" + "github.com/prysmaticlabs/prysm/v5/time/slots" + "google.golang.org/protobuf/proto" ) -func TestStore_LightClientUpdate_CanSaveRetrieveAltair(t *testing.T) { - db := setupDB(t) - ctx := context.Background() - update := ðpbv2.LightClientUpdate{ - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 1, - ParentRoot: []byte{1, 1, 1}, - StateRoot: []byte{1, 1, 1}, - BodyRoot: []byte{1, 1, 1}, - }, - }, - }, - }, - NextSyncCommittee: ðpbv2.SyncCommittee{ - Pubkeys: nil, - AggregatePubkey: nil, - }, - NextSyncCommitteeBranch: nil, - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 1, - ParentRoot: []byte{1, 1, 1}, - StateRoot: []byte{1, 1, 1}, - BodyRoot: []byte{1, 1, 1}, - }, - }, - }, - }, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, +func createUpdate(t *testing.T, v int) (interfaces.LightClientUpdate, error) { + config := params.BeaconConfig() + var slot primitives.Slot + var header interfaces.LightClientHeader + var st state.BeaconState + var err error + + sampleRoot := make([]byte, 32) + for i := 0; i < 32; i++ { + sampleRoot[i] = byte(i) } - period := uint64(1) - err := db.SaveLightClientUpdate(ctx, period, ðpbv2.LightClientUpdateWithVersion{ - Version: version.Altair, - Data: update, - }) - require.NoError(t, err) - retrievedUpdate, err := db.LightClientUpdate(ctx, period) - require.NoError(t, err) - require.DeepEqual(t, update, retrievedUpdate.Data, "retrieved update does not match saved update") -} + sampleExecutionBranch := make([][]byte, fieldparams.ExecutionBranchDepth) + for i := 0; i < 4; i++ { + sampleExecutionBranch[i] = make([]byte, 32) + for j := 0; j < 32; j++ { + sampleExecutionBranch[i][j] = byte(i + j) + } + } -func TestStore_LightClientUpdate_CanSaveRetrieveCapella(t *testing.T) { - db := setupDB(t) - ctx := context.Background() - update := ðpbv2.LightClientUpdate{ - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ - HeaderCapella: ðpbv2.LightClientHeaderCapella{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 1, - ParentRoot: []byte{1, 1, 1}, - StateRoot: []byte{1, 1, 1}, - BodyRoot: []byte{1, 1, 1}, - }, - Execution: &enginev1.ExecutionPayloadHeaderCapella{ - FeeRecipient: []byte{1, 2, 3}, - }, - ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, - }, - }, - }, - NextSyncCommittee: ðpbv2.SyncCommittee{ - Pubkeys: nil, - AggregatePubkey: nil, - }, - NextSyncCommitteeBranch: nil, - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ - HeaderCapella: ðpbv2.LightClientHeaderCapella{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 1, - ParentRoot: []byte{1, 1, 1}, - StateRoot: []byte{1, 1, 1}, - BodyRoot: []byte{1, 1, 1}, - }, - Execution: nil, - ExecutionBranch: nil, - }, + switch v { + case version.Altair: + slot = primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, }, - }, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, + }) + require.NoError(t, err) + st, err = util.NewBeaconState() + require.NoError(t, err) + case version.Capella: + slot = primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderCapella{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + }, + ExecutionBranch: sampleExecutionBranch, + }) + require.NoError(t, err) + st, err = util.NewBeaconStateCapella() + require.NoError(t, err) + case version.Deneb: + slot = primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, + }, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + }, + ExecutionBranch: sampleExecutionBranch, + }) + require.NoError(t, err) + st, err = util.NewBeaconStateDeneb() + require.NoError(t, err) + case version.Electra: + slot = primitives.Slot(config.ElectraForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, + }, + Execution: &enginev1.ExecutionPayloadHeaderElectra{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + }, + ExecutionBranch: sampleExecutionBranch, + }) + require.NoError(t, err) + st, err = util.NewBeaconStateElectra() + require.NoError(t, err) + default: + return nil, fmt.Errorf("unsupported version %s", version.String(v)) } - period := uint64(1) - err := db.SaveLightClientUpdate(ctx, period, ðpbv2.LightClientUpdateWithVersion{ - Version: version.Capella, - Data: update, - }) - require.NoError(t, err) - retrievedUpdate, err := db.LightClientUpdate(ctx, period) + update, err := createDefaultLightClientUpdate(slot, st) require.NoError(t, err) - require.DeepEqual(t, update, retrievedUpdate.Data, "retrieved update does not match saved update") + update.SetSignatureSlot(slot - 1) + syncCommitteeBits := make([]byte, 64) + syncCommitteeSignature := make([]byte, 96) + update.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: syncCommitteeBits, + SyncCommitteeSignature: syncCommitteeSignature, + }) + + require.NoError(t, update.SetAttestedHeader(header)) + require.NoError(t, update.SetFinalizedHeader(header)) + + return update, nil } -func TestStore_LightClientUpdate_CanSaveRetrieveDeneb(t *testing.T) { +func TestStore_LightClientUpdate_CanSaveRetrieve(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.BeaconConfig() + cfg.AltairForkEpoch = 0 + cfg.CapellaForkEpoch = 1 + cfg.DenebForkEpoch = 2 + cfg.ElectraForkEpoch = 3 + params.OverrideBeaconConfig(cfg) + db := setupDB(t) ctx := context.Background() - update := ðpbv2.LightClientUpdate{ - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ - HeaderDeneb: ðpbv2.LightClientHeaderDeneb{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 1, - ParentRoot: []byte{1, 1, 1}, - StateRoot: []byte{1, 1, 1}, - BodyRoot: []byte{1, 1, 1}, - }, - Execution: &enginev1.ExecutionPayloadHeaderDeneb{ - FeeRecipient: []byte{1, 2, 3}, - }, - ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, - }, - }, - }, - NextSyncCommittee: ðpbv2.SyncCommittee{ - Pubkeys: nil, - AggregatePubkey: nil, - }, - NextSyncCommitteeBranch: nil, - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ - HeaderDeneb: ðpbv2.LightClientHeaderDeneb{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 1, - ParentRoot: []byte{1, 1, 1}, - StateRoot: []byte{1, 1, 1}, - BodyRoot: []byte{1, 1, 1}, - }, - Execution: nil, - ExecutionBranch: nil, - }, - }, - }, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - } - period := uint64(1) - err := db.SaveLightClientUpdate(ctx, period, ðpbv2.LightClientUpdateWithVersion{ - Version: version.Deneb, - Data: update, + + t.Run("Altair", func(t *testing.T) { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + period := uint64(1) + + err = db.SaveLightClientUpdate(ctx, period, update) + require.NoError(t, err) + + retrievedUpdate, err := db.LightClientUpdate(ctx, period) + require.NoError(t, err) + require.DeepEqual(t, update, retrievedUpdate, "retrieved update does not match saved update") }) - require.NoError(t, err) + t.Run("Capella", func(t *testing.T) { + update, err := createUpdate(t, version.Capella) + require.NoError(t, err) + period := uint64(1) + err = db.SaveLightClientUpdate(ctx, period, update) + require.NoError(t, err) - retrievedUpdate, err := db.LightClientUpdate(ctx, period) - require.NoError(t, err) - require.DeepEqual(t, update, retrievedUpdate.Data, "retrieved update does not match saved update") + retrievedUpdate, err := db.LightClientUpdate(ctx, period) + require.NoError(t, err) + require.DeepEqual(t, update, retrievedUpdate, "retrieved update does not match saved update") + }) + t.Run("Deneb", func(t *testing.T) { + update, err := createUpdate(t, version.Deneb) + require.NoError(t, err) + period := uint64(1) + err = db.SaveLightClientUpdate(ctx, period, update) + require.NoError(t, err) + + retrievedUpdate, err := db.LightClientUpdate(ctx, period) + require.NoError(t, err) + require.DeepEqual(t, update, retrievedUpdate, "retrieved update does not match saved update") + }) + t.Run("Electra", func(t *testing.T) { + update, err := createUpdate(t, version.Electra) + require.NoError(t, err) + period := uint64(1) + err = db.SaveLightClientUpdate(ctx, period, update) + require.NoError(t, err) + + retrievedUpdate, err := db.LightClientUpdate(ctx, period) + require.NoError(t, err) + require.DeepEqual(t, update, retrievedUpdate, "retrieved update does not match saved update") + }) } func TestStore_LightClientUpdates_canRetrieveRange(t *testing.T) { db := setupDB(t) ctx := context.Background() - updates := []*ethpbv2.LightClientUpdateWithVersion{ - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 8, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 9, - }, - }, + updates := make([]interfaces.LightClientUpdate, 0, 3) + for i := 1; i <= 3; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) } for i, update := range updates { @@ -229,7 +239,7 @@ func TestStore_LightClientUpdates_canRetrieveRange(t *testing.T) { require.NoError(t, err) require.Equal(t, len(updates), len(retrievedUpdatesMap), "retrieved updates do not match saved updates") for i, update := range updates { - require.Equal(t, update.Data.SignatureSlot, retrievedUpdatesMap[uint64(i+1)].Data.SignatureSlot, "retrieved update does not match saved update") + require.DeepEqual(t, update, retrievedUpdatesMap[uint64(i+1)], "retrieved update does not match saved update") } } @@ -237,43 +247,11 @@ func TestStore_LightClientUpdates_canRetrieveRange(t *testing.T) { func TestStore_LightClientUpdate_EndPeriodSmallerThanStartPeriod(t *testing.T) { db := setupDB(t) ctx := context.Background() - updates := []*ethpbv2.LightClientUpdateWithVersion{ - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 8, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 9, - }, - }, + updates := make([]interfaces.LightClientUpdate, 0, 3) + for i := 1; i <= 3; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) } for i, update := range updates { @@ -292,43 +270,11 @@ func TestStore_LightClientUpdate_EndPeriodSmallerThanStartPeriod(t *testing.T) { func TestStore_LightClientUpdate_EndPeriodEqualToStartPeriod(t *testing.T) { db := setupDB(t) ctx := context.Background() - updates := []*ethpbv2.LightClientUpdateWithVersion{ - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 8, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 9, - }, - }, + updates := make([]interfaces.LightClientUpdate, 0, 3) + for i := 1; i <= 3; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) } for i, update := range updates { @@ -340,53 +286,21 @@ func TestStore_LightClientUpdate_EndPeriodEqualToStartPeriod(t *testing.T) { retrievedUpdates, err := db.LightClientUpdates(ctx, 2, 2) require.NoError(t, err) require.Equal(t, 1, len(retrievedUpdates)) - require.Equal(t, updates[1].Data.SignatureSlot, retrievedUpdates[2].Data.SignatureSlot, "retrieved update does not match saved update") + require.DeepEqual(t, updates[1], retrievedUpdates[2], "retrieved update does not match saved update") } func TestStore_LightClientUpdate_StartPeriodBeforeFirstUpdate(t *testing.T) { db := setupDB(t) ctx := context.Background() - updates := []*ethpbv2.LightClientUpdateWithVersion{ - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 8, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 9, - }, - }, + updates := make([]interfaces.LightClientUpdate, 0, 3) + for i := 1; i <= 3; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) } for i, update := range updates { - err := db.SaveLightClientUpdate(ctx, uint64(i+2), update) + err := db.SaveLightClientUpdate(ctx, uint64(i+1), update) require.NoError(t, err) } @@ -395,50 +309,18 @@ func TestStore_LightClientUpdate_StartPeriodBeforeFirstUpdate(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, len(retrievedUpdates)) for i, update := range updates { - require.Equal(t, update.Data.SignatureSlot, retrievedUpdates[uint64(i+2)].Data.SignatureSlot, "retrieved update does not match saved update") + require.DeepEqual(t, update, retrievedUpdates[uint64(i+1)], "retrieved update does not match saved update") } } func TestStore_LightClientUpdate_EndPeriodAfterLastUpdate(t *testing.T) { db := setupDB(t) ctx := context.Background() - updates := []*ethpbv2.LightClientUpdateWithVersion{ - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 8, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 9, - }, - }, + updates := make([]interfaces.LightClientUpdate, 0, 3) + for i := 1; i <= 3; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) } for i, update := range updates { @@ -451,50 +333,18 @@ func TestStore_LightClientUpdate_EndPeriodAfterLastUpdate(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, len(retrievedUpdates)) for i, update := range updates { - require.Equal(t, update.Data.SignatureSlot, retrievedUpdates[uint64(i+1)].Data.SignatureSlot, "retrieved update does not match saved update") + require.DeepEqual(t, update, retrievedUpdates[uint64(i+1)], "retrieved update does not match saved update") } } func TestStore_LightClientUpdate_PartialUpdates(t *testing.T) { db := setupDB(t) ctx := context.Background() - updates := []*ethpbv2.LightClientUpdateWithVersion{ - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 8, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 9, - }, - }, + updates := make([]interfaces.LightClientUpdate, 0, 3) + for i := 1; i <= 3; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) } for i, update := range updates { @@ -507,100 +357,53 @@ func TestStore_LightClientUpdate_PartialUpdates(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(retrievedUpdates)) for i, update := range updates[:2] { - require.Equal(t, update.Data.SignatureSlot, retrievedUpdates[uint64(i+1)].Data.SignatureSlot, "retrieved update does not match saved update") + require.DeepEqual(t, update, retrievedUpdates[uint64(i+1)], "retrieved update does not match saved update") } } func TestStore_LightClientUpdate_MissingPeriods_SimpleData(t *testing.T) { db := setupDB(t) ctx := context.Background() - updates := []*ethpbv2.LightClientUpdateWithVersion{ - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 8, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 11, - }, - }, - { - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 12, - }, - }, + updates := make([]interfaces.LightClientUpdate, 0, 4) + for i := 1; i <= 4; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) } - for _, update := range updates { - err := db.SaveLightClientUpdate(ctx, uint64(update.Data.SignatureSlot), update) + for i, update := range updates { + if i == 1 || i == 2 { + continue + } + err := db.SaveLightClientUpdate(ctx, uint64(i+1), update) require.NoError(t, err) } // Retrieve the updates - retrievedUpdates, err := db.LightClientUpdates(ctx, 7, 12) + retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 4) require.NoError(t, err) - require.Equal(t, 4, len(retrievedUpdates)) - for _, update := range updates { - require.Equal(t, update.Data.SignatureSlot, retrievedUpdates[uint64(update.Data.SignatureSlot)].Data.SignatureSlot, "retrieved update does not match saved update") - } + require.Equal(t, 2, len(retrievedUpdates)) + require.DeepEqual(t, updates[0], retrievedUpdates[uint64(1)], "retrieved update does not match saved update") + require.DeepEqual(t, updates[3], retrievedUpdates[uint64(4)], "retrieved update does not match saved update") // Retrieve the updates from the middle - retrievedUpdates, err = db.LightClientUpdates(ctx, 8, 12) + retrievedUpdates, err = db.LightClientUpdates(ctx, 2, 4) require.NoError(t, err) - require.Equal(t, 3, len(retrievedUpdates)) - require.Equal(t, updates[1].Data.SignatureSlot, retrievedUpdates[8].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, updates[2].Data.SignatureSlot, retrievedUpdates[11].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, updates[3].Data.SignatureSlot, retrievedUpdates[12].Data.SignatureSlot, "retrieved update does not match saved update") + require.Equal(t, 1, len(retrievedUpdates)) + require.DeepEqual(t, updates[3], retrievedUpdates[4], "retrieved update does not match saved update") // Retrieve the updates from after the missing period - retrievedUpdates, err = db.LightClientUpdates(ctx, 11, 12) + retrievedUpdates, err = db.LightClientUpdates(ctx, 4, 4) require.NoError(t, err) - require.Equal(t, 2, len(retrievedUpdates)) - require.Equal(t, updates[2].Data.SignatureSlot, retrievedUpdates[11].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, updates[3].Data.SignatureSlot, retrievedUpdates[12].Data.SignatureSlot, "retrieved update does not match saved update") + require.Equal(t, 1, len(retrievedUpdates)) + require.DeepEqual(t, updates[3], retrievedUpdates[4], "retrieved update does not match saved update") //retrieve the updates from before the missing period to after the missing period - retrievedUpdates, err = db.LightClientUpdates(ctx, 3, 15) + retrievedUpdates, err = db.LightClientUpdates(ctx, 0, 6) require.NoError(t, err) - require.Equal(t, 4, len(retrievedUpdates)) - require.Equal(t, updates[0].Data.SignatureSlot, retrievedUpdates[7].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, updates[1].Data.SignatureSlot, retrievedUpdates[8].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, updates[2].Data.SignatureSlot, retrievedUpdates[11].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, updates[3].Data.SignatureSlot, retrievedUpdates[12].Data.SignatureSlot, "retrieved update does not match saved update") + require.Equal(t, 2, len(retrievedUpdates)) + require.DeepEqual(t, updates[0], retrievedUpdates[uint64(1)], "retrieved update does not match saved update") + require.DeepEqual(t, updates[3], retrievedUpdates[uint64(4)], "retrieved update does not match saved update") } func TestStore_LightClientUpdate_EmptyDB(t *testing.T) { @@ -613,177 +416,126 @@ func TestStore_LightClientUpdate_EmptyDB(t *testing.T) { require.Equal(t, 0, len(retrievedUpdates)) } -func TestStore_LightClientUpdate_MissingPeriodsAtTheEnd_SimpleData(t *testing.T) { +func TestStore_LightClientUpdate_RetrieveMissingPeriodDistributed(t *testing.T) { db := setupDB(t) ctx := context.Background() - - for i := 1; i < 4; i++ { - update := ðpbv2.LightClientUpdateWithVersion{ - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: primitives.Slot(uint64(i)), - }, - } - err := db.SaveLightClientUpdate(ctx, uint64(i), update) + updates := make([]interfaces.LightClientUpdate, 0, 5) + for i := 1; i <= 5; i++ { + update, err := createUpdate(t, version.Altair) require.NoError(t, err) + updates = append(updates, update) } - for i := 7; i < 10; i++ { - update := ðpbv2.LightClientUpdateWithVersion{ - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: primitives.Slot(uint64(i)), - }, + + for i, update := range updates { + if i == 1 || i == 3 { + continue } - err := db.SaveLightClientUpdate(ctx, uint64(i), update) + err := db.SaveLightClientUpdate(ctx, uint64(i+1), update) require.NoError(t, err) } - // Retrieve the updates from 1 to 5 - retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 5) + // Retrieve the updates + retrievedUpdates, err := db.LightClientUpdates(ctx, 0, 7) require.NoError(t, err) require.Equal(t, 3, len(retrievedUpdates)) - require.Equal(t, primitives.Slot(1), retrievedUpdates[1].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, primitives.Slot(2), retrievedUpdates[2].Data.SignatureSlot, "retrieved update does not match saved update") - require.Equal(t, primitives.Slot(3), retrievedUpdates[3].Data.SignatureSlot, "retrieved update does not match saved update") - + require.DeepEqual(t, updates[0], retrievedUpdates[uint64(1)], "retrieved update does not match saved update") + require.DeepEqual(t, updates[2], retrievedUpdates[uint64(3)], "retrieved update does not match saved update") + require.DeepEqual(t, updates[4], retrievedUpdates[uint64(5)], "retrieved update does not match saved update") } -func setupLightClientTestDB(t *testing.T) (*Store, context.Context) { - db := setupDB(t) - ctx := context.Background() +func createDefaultLightClientUpdate(currentSlot primitives.Slot, attestedState state.BeaconState) (interfaces.LightClientUpdate, error) { + currentEpoch := slots.ToEpoch(currentSlot) - for i := 10; i < 101; i++ { // 10 to 100 - update := ðpbv2.LightClientUpdateWithVersion{ - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: primitives.Slot(uint64(i)), - }, - } - err := db.SaveLightClientUpdate(ctx, uint64(i), update) - require.NoError(t, err) + syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize + pubKeys := make([][]byte, syncCommitteeSize) + for i := uint64(0); i < syncCommitteeSize; i++ { + pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength) } - - for i := 110; i < 201; i++ { // 110 to 200 - update := ðpbv2.LightClientUpdateWithVersion{ - Version: 1, - Data: ðpbv2.LightClientUpdate{ - AttestedHeader: nil, - NextSyncCommittee: nil, - NextSyncCommitteeBranch: nil, - FinalizedHeader: nil, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: primitives.Slot(uint64(i)), - }, - } - err := db.SaveLightClientUpdate(ctx, uint64(i), update) - require.NoError(t, err) + nextSyncCommittee := &pb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength), } - return db, ctx -} - -func TestStore_LightClientUpdate_MissingPeriodsInTheMiddleDistributed(t *testing.T) { - db, ctx := setupLightClientTestDB(t) - - // Retrieve the updates - should fail because of missing periods in the middle - retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 300) - require.NoError(t, err) - require.Equal(t, 91*2, len(retrievedUpdates)) - for i := 10; i < 101; i++ { - require.Equal(t, primitives.Slot(uint64(i)), retrievedUpdates[uint64(i)].Data.SignatureSlot, "retrieved update does not match saved update") + var nextSyncCommitteeBranch [][]byte + if attestedState.Version() >= version.Electra { + nextSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepthElectra) + } else { + nextSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepth) } - for i := 110; i < 201; i++ { - require.Equal(t, primitives.Slot(uint64(i)), retrievedUpdates[uint64(i)].Data.SignatureSlot, "retrieved update does not match saved update") + for i := 0; i < len(nextSyncCommitteeBranch); i++ { + nextSyncCommitteeBranch[i] = make([]byte, fieldparams.RootLength) } -} - -func TestStore_LightClientUpdate_RetrieveValidRangeFromStart(t *testing.T) { - db, ctx := setupLightClientTestDB(t) - - // retrieve 1 to 100 - should work because all periods are present after the firstPeriodInDB > startPeriod - retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 100) - require.NoError(t, err) - require.Equal(t, 91, len(retrievedUpdates)) - for i := 10; i < 101; i++ { - require.Equal(t, primitives.Slot(uint64(i)), retrievedUpdates[uint64(i)].Data.SignatureSlot, "retrieved update does not match saved update") + executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth) + for i := 0; i < fieldparams.ExecutionBranchDepth; i++ { + executionBranch[i] = make([]byte, 32) } -} - -func TestStore_LightClientUpdate_RetrieveValidRangeInTheMiddle(t *testing.T) { - db, ctx := setupLightClientTestDB(t) - // retrieve 110 to 200 - should work because all periods are present - retrievedUpdates, err := db.LightClientUpdates(ctx, 110, 200) - require.NoError(t, err) - require.Equal(t, 91, len(retrievedUpdates)) - for i := 110; i < 201; i++ { - require.Equal(t, primitives.Slot(uint64(i)), retrievedUpdates[uint64(i)].Data.SignatureSlot, "retrieved update does not match saved update") + var finalityBranch [][]byte + if attestedState.Version() >= version.Electra { + finalityBranch = make([][]byte, fieldparams.FinalityBranchDepthElectra) + } else { + finalityBranch = make([][]byte, fieldparams.FinalityBranchDepth) } -} - -func TestStore_LightClientUpdate_MissingPeriodInTheMiddleConcentrated(t *testing.T) { - db, ctx := setupLightClientTestDB(t) - - // retrieve 100 to 200 - retrievedUpdates, err := db.LightClientUpdates(ctx, 100, 200) - require.NoError(t, err) - require.Equal(t, 92, len(retrievedUpdates)) - require.Equal(t, primitives.Slot(100), retrievedUpdates[100].Data.SignatureSlot, "retrieved update does not match saved update") - for i := 110; i < 201; i++ { - require.Equal(t, primitives.Slot(uint64(i)), retrievedUpdates[uint64(i)].Data.SignatureSlot, "retrieved update does not match saved update") + for i := 0; i < len(finalityBranch); i++ { + finalityBranch[i] = make([]byte, 32) } -} -func TestStore_LightClientUpdate_MissingPeriodsAtTheEnd(t *testing.T) { - db, ctx := setupLightClientTestDB(t) - - // retrieve 10 to 109 - retrievedUpdates, err := db.LightClientUpdates(ctx, 10, 109) - require.NoError(t, err) - require.Equal(t, 91, len(retrievedUpdates)) - for i := 10; i < 101; i++ { - require.Equal(t, primitives.Slot(uint64(i)), retrievedUpdates[uint64(i)].Data.SignatureSlot, "retrieved update does not match saved update") - } -} - -func TestStore_LightClientUpdate_MissingPeriodsAtTheBeginning(t *testing.T) { - db, ctx := setupLightClientTestDB(t) - - // retrieve 105 to 200 - retrievedUpdates, err := db.LightClientUpdates(ctx, 105, 200) - require.NoError(t, err) - require.Equal(t, 91, len(retrievedUpdates)) - for i := 110; i < 201; i++ { - require.Equal(t, primitives.Slot(uint64(i)), retrievedUpdates[uint64(i)].Data.SignatureSlot, "retrieved update does not match saved update") + var m proto.Message + if currentEpoch < params.BeaconConfig().CapellaForkEpoch { + m = &pb.LightClientUpdateAltair{ + AttestedHeader: &pb.LightClientHeaderAltair{}, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else if currentEpoch < params.BeaconConfig().DenebForkEpoch { + m = &pb.LightClientUpdateCapella{ + AttestedHeader: &pb.LightClientHeaderCapella{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderCapella{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else if currentEpoch < params.BeaconConfig().ElectraForkEpoch { + m = &pb.LightClientUpdateDeneb{ + AttestedHeader: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else { + if attestedState.Version() >= version.Electra { + m = &pb.LightClientUpdateElectra{ + AttestedHeader: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } else { + m = &pb.LightClientUpdateDeneb{ + AttestedHeader: &pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{}, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{}, + ExecutionBranch: executionBranch, + }, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, + } + } } -} - -func TestStore_LightClientUpdate_StartPeriodGreaterThanLastPeriod(t *testing.T) { - db, ctx := setupLightClientTestDB(t) - - // retrieve 300 to 400 - retrievedUpdates, err := db.LightClientUpdates(ctx, 300, 400) - require.NoError(t, err) - require.Equal(t, 0, len(retrievedUpdates)) + return light_client.NewWrappedUpdate(m) } diff --git a/beacon-chain/db/kv/schema.go b/beacon-chain/db/kv/schema.go index 30d950514ca2..f6648a8f928a 100644 --- a/beacon-chain/db/kv/schema.go +++ b/beacon-chain/db/kv/schema.go @@ -18,7 +18,8 @@ var ( registrationBucket = []byte("registration") // Light Client Updates Bucket - lightClientUpdatesBucket = []byte("light-client-updates") + lightClientUpdatesBucket = []byte("light-client-updates") + lightClientBootstrapBucket = []byte("light-client-bootstrap") // Deprecated: This bucket was migrated in PR 6461. Do not use, except for migrations. slotsHasObjectBucket = []byte("slots-has-objects") diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go index 0b853cac1080..1424ab93750e 100644 --- a/beacon-chain/execution/engine_client.go +++ b/beacon-chain/execution/engine_client.go @@ -801,7 +801,7 @@ func tDStringToUint256(td string) (*uint256.Int, error) { return i, nil } -func buildEmptyExecutionPayload(v int) (proto.Message, error) { +func EmptyExecutionPayload(v int) (proto.Message, error) { switch v { case version.Bellatrix: return &pb.ExecutionPayload{ diff --git a/beacon-chain/execution/payload_body.go b/beacon-chain/execution/payload_body.go index 17aba3329e0f..4021bb2339f2 100644 --- a/beacon-chain/execution/payload_body.go +++ b/beacon-chain/execution/payload_body.go @@ -205,7 +205,7 @@ func (r *blindedBlockReconstructor) requestBodiesByHash(ctx context.Context, cli func (r *blindedBlockReconstructor) payloadForHeader(header interfaces.ExecutionData, v int) (proto.Message, error) { bodyKey := bytesutil.ToBytes32(header.BlockHash()) if bodyKey == params.BeaconConfig().ZeroHash { - payload, err := buildEmptyExecutionPayload(v) + payload, err := EmptyExecutionPayload(v) if err != nil { return nil, errors.Wrapf(err, "failed to reconstruct payload for body hash %#x", bodyKey) } diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index 682fd1f00286..6e90360f5ae1 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -918,10 +918,11 @@ func (*Service) configEndpoints() []endpoint { func (s *Service) lightClientEndpoints(blocker lookup.Blocker, stater lookup.Stater) []endpoint { server := &lightclient.Server{ - Blocker: blocker, - Stater: stater, - HeadFetcher: s.cfg.HeadFetcher, - BeaconDB: s.cfg.BeaconDB, + Blocker: blocker, + Stater: stater, + HeadFetcher: s.cfg.HeadFetcher, + ChainInfoFetcher: s.cfg.ChainInfoFetcher, + BeaconDB: s.cfg.BeaconDB, } const namespace = "lightclient" diff --git a/beacon-chain/rpc/eth/events/BUILD.bazel b/beacon-chain/rpc/eth/events/BUILD.bazel index 5624b7d5ee8f..d2a1e918b025 100644 --- a/beacon-chain/rpc/eth/events/BUILD.bazel +++ b/beacon-chain/rpc/eth/events/BUILD.bazel @@ -20,13 +20,13 @@ go_library( "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/time:go_default_library", "//config/params:go_default_library", + "//consensus-types/interfaces:go_default_library", "//consensus-types/payload-attribute:go_default_library", "//consensus-types/primitives:go_default_library", "//monitoring/tracing/trace:go_default_library", "//network/httputil:go_default_library", "//proto/engine/v1:go_default_library", "//proto/eth/v1:go_default_library", - "//proto/eth/v2:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//runtime/version:go_default_library", "//time/slots:go_default_library", diff --git a/beacon-chain/rpc/eth/events/events.go b/beacon-chain/rpc/eth/events/events.go index 6156bb07d03e..6caf6a9d6b6f 100644 --- a/beacon-chain/rpc/eth/events/events.go +++ b/beacon-chain/rpc/eth/events/events.go @@ -22,13 +22,13 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" chaintime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" payloadattribute "github.com/prysmaticlabs/prysm/v5/consensus-types/payload-attribute" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" "github.com/prysmaticlabs/prysm/v5/network/httputil" engine "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/time/slots" @@ -431,9 +431,9 @@ func topicForEvent(event *feed.Event) string { return HeadTopic case *ethpb.EventFinalizedCheckpoint: return FinalizedCheckpointTopic - case *ethpbv2.LightClientFinalityUpdateWithVersion: + case interfaces.LightClientFinalityUpdate: return LightClientFinalityUpdateTopic - case *ethpbv2.LightClientOptimisticUpdateWithVersion: + case interfaces.LightClientOptimisticUpdate: return LightClientOptimisticUpdateTopic case *ethpb.EventChainReorg: return ChainReorgTopic @@ -527,25 +527,25 @@ func (s *Server) lazyReaderForEvent(ctx context.Context, event *feed.Event, topi return func() io.Reader { return jsonMarshalReader(eventName, structs.FinalizedCheckpointEventFromV1(v)) }, nil - case *ethpbv2.LightClientFinalityUpdateWithVersion: - cv, err := structs.LightClientFinalityUpdateFromConsensus(v.Data) + case interfaces.LightClientFinalityUpdate: + cv, err := structs.LightClientFinalityUpdateFromConsensus(v) if err != nil { - return nil, errors.Wrap(err, "LightClientFinalityUpdateWithVersion event conversion failure") + return nil, errors.Wrap(err, "LightClientFinalityUpdate conversion failure") } ev := &structs.LightClientFinalityUpdateEvent{ - Version: version.String(int(v.Version)), + Version: version.String(v.Version()), Data: cv, } return func() io.Reader { return jsonMarshalReader(eventName, ev) }, nil - case *ethpbv2.LightClientOptimisticUpdateWithVersion: - cv, err := structs.LightClientOptimisticUpdateFromConsensus(v.Data) + case interfaces.LightClientOptimisticUpdate: + cv, err := structs.LightClientOptimisticUpdateFromConsensus(v) if err != nil { - return nil, errors.Wrap(err, "LightClientOptimisticUpdateWithVersion event conversion failure") + return nil, errors.Wrap(err, "LightClientOptimisticUpdate conversion failure") } ev := &structs.LightClientOptimisticUpdateEvent{ - Version: version.String(int(v.Version)), + Version: version.String(v.Version()), Data: cv, } return func() io.Reader { diff --git a/beacon-chain/rpc/eth/light-client/BUILD.bazel b/beacon-chain/rpc/eth/light-client/BUILD.bazel index 85b46a07ecc4..4b668ff2ee2a 100644 --- a/beacon-chain/rpc/eth/light-client/BUILD.bazel +++ b/beacon-chain/rpc/eth/light-client/BUILD.bazel @@ -18,16 +18,12 @@ go_library( "//beacon-chain/rpc/eth/shared:go_default_library", "//beacon-chain/rpc/lookup:go_default_library", "//beacon-chain/state:go_default_library", - "//config/fieldparams:go_default_library", "//config/params:go_default_library", "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", "//monitoring/tracing/trace:go_default_library", "//network/httputil:go_default_library", - "//proto/eth/v2:go_default_library", - "//proto/migration:go_default_library", "//runtime/version:go_default_library", - "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_wealdtech_go_bytesutil//:go_default_library", @@ -46,16 +42,18 @@ go_test( "//beacon-chain/blockchain/testing:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/light-client:go_default_library", + "//beacon-chain/db/testing:go_default_library", "//beacon-chain/rpc/testutil:go_default_library", "//beacon-chain/state:go_default_library", "//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", - "//proto/eth/v1:go_default_library", - "//proto/eth/v2:go_default_library", + "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 245d703eaafd..7e0d7a1d7a12 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -10,11 +10,10 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api/server/structs" + lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" - types "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" "github.com/prysmaticlabs/prysm/v5/network/httputil" "github.com/prysmaticlabs/prysm/v5/runtime/version" @@ -47,18 +46,32 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques return } - bootstrap, err := createLightClientBootstrap(ctx, state, blk) + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(ctx, s.ChainInfoFetcher.CurrentSlot(), state, blk) if err != nil { httputil.HandleError(w, "could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError) return } - response := &structs.LightClientBootstrapResponse{ - Version: version.String(blk.Version()), - Data: bootstrap, - } - w.Header().Set(api.VersionHeader, version.String(version.Deneb)) + w.Header().Set(api.VersionHeader, version.String(bootstrap.Version())) - httputil.WriteJson(w, response) + if httputil.RespondWithSsz(req) { + ssz, err := bootstrap.MarshalSSZ() + if err != nil { + httputil.HandleError(w, "could not marshal bootstrap to SSZ: "+err.Error(), http.StatusInternalServerError) + return + } + httputil.WriteSsz(w, ssz, "light_client_bootstrap.ssz") + } else { + data, err := structs.LightClientBootstrapFromConsensus(bootstrap) + if err != nil { + httputil.HandleError(w, "could not marshal bootstrap to JSON: "+err.Error(), http.StatusInternalServerError) + return + } + response := &structs.LightClientBootstrapResponse{ + Version: version.String(bootstrap.Version()), + Data: data, + } + httputil.WriteJson(w, response) + } } // GetLightClientUpdatesByRange - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/updates.yaml @@ -117,110 +130,33 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R endPeriod = maxSlot / slotsPerPeriod } - // Populate updates - var updates []*structs.LightClientUpdateResponse - for period := startPeriod; period <= endPeriod; period++ { - // Get the last known state of the period, - // 1. We wish the block has a parent in the same period if possible - // 2. We wish the block has a state in the same period - lastSlotInPeriod := period*slotsPerPeriod + slotsPerPeriod - 1 - if lastSlotInPeriod > maxSlot { - lastSlotInPeriod = maxSlot - } - firstSlotInPeriod := period * slotsPerPeriod - - // Let's not use the first slot in the period, otherwise the attested header will be in previous period - firstSlotInPeriod++ - - var state state.BeaconState - var block interfaces.ReadOnlySignedBeaconBlock - for slot := lastSlotInPeriod; slot >= firstSlotInPeriod; slot-- { - state, err = s.Stater.StateBySlot(ctx, types.Slot(slot)) - if err != nil { - continue - } - - // Get the block - latestBlockHeader := state.LatestBlockHeader() - latestStateRoot, err := state.HashTreeRoot(ctx) - if err != nil { - continue - } - latestBlockHeader.StateRoot = latestStateRoot[:] - blockRoot, err := latestBlockHeader.HashTreeRoot() - if err != nil { - continue - } - - block, err = s.Blocker.Block(ctx, blockRoot[:]) - if err != nil || block == nil { - continue - } - - syncAggregate, err := block.Block().Body().SyncAggregate() - if err != nil || syncAggregate == nil { - continue - } - - if syncAggregate.SyncCommitteeBits.Count()*3 < config.SyncCommitteeSize*2 { - // Not enough votes - continue - } - - break - } + // get updates + updatesMap, err := s.BeaconDB.LightClientUpdates(ctx, startPeriod, endPeriod) + if err != nil { + httputil.HandleError(w, "Could not get light client updates from DB: "+err.Error(), http.StatusInternalServerError) + return + } - if block == nil { - // No valid block found for the period - continue - } + updates := make([]*structs.LightClientUpdateResponse, 0, len(updatesMap)) - // Get attested state - attestedRoot := block.Block().ParentRoot() - attestedBlock, err := s.Blocker.Block(ctx, attestedRoot[:]) - if err != nil || attestedBlock == nil { - continue + for i := startPeriod; i <= endPeriod; i++ { + update, ok := updatesMap[i] + if !ok { + // Only return the first contiguous range of updates + break } - attestedSlot := attestedBlock.Block().Slot() - attestedState, err := s.Stater.StateBySlot(ctx, attestedSlot) + updateJson, err := structs.LightClientUpdateFromConsensus(update) if err != nil { - continue - } - - // Get finalized block - var finalizedBlock interfaces.ReadOnlySignedBeaconBlock - finalizedCheckPoint := attestedState.FinalizedCheckpoint() - if finalizedCheckPoint != nil { - finalizedRoot := bytesutil.ToBytes32(finalizedCheckPoint.Root) - finalizedBlock, err = s.Blocker.Block(ctx, finalizedRoot[:]) - if err != nil { - finalizedBlock = nil - } + httputil.HandleError(w, "Could not convert light client update: "+err.Error(), http.StatusInternalServerError) + return } - - update, err := newLightClientUpdateFromBeaconState( - ctx, - state, - block, - attestedState, - attestedBlock, - finalizedBlock, - ) - - if err == nil { - updates = append(updates, &structs.LightClientUpdateResponse{ - Version: version.String(attestedState.Version()), - Data: update, - }) + updateResponse := &structs.LightClientUpdateResponse{ + Version: version.String(update.Version()), + Data: updateJson, } + updates = append(updates, updateResponse) } - - if len(updates) == 0 { - httputil.HandleError(w, "no updates found", http.StatusNotFound) - return - } - httputil.WriteJson(w, updates) } @@ -268,7 +204,7 @@ func (s *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.R return } - update, err := newLightClientFinalityUpdateFromBeaconState(ctx, st, block, attestedState, attestedBlock, finalizedBlock) + 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 @@ -313,7 +249,7 @@ func (s *Server) GetLightClientOptimisticUpdate(w http.ResponseWriter, req *http return } - update, err := newLightClientOptimisticUpdateFromBeaconState(ctx, st, block, attestedState, attestedBlock) + 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 diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go index 342ea679ee3c..984cd7add016 100644 --- a/beacon-chain/rpc/eth/light-client/handlers_test.go +++ b/beacon-chain/rpc/eth/light-client/handlers_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "net/http" "net/http/httptest" "strconv" @@ -14,304 +15,613 @@ import ( "github.com/prysmaticlabs/prysm/v5/api/server/structs" mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" + dbtesting "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + 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" - ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" ) -func TestLightClientHandler_GetLightClientBootstrap_Altair(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestAltair() +func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.BeaconConfig() + cfg.AltairForkEpoch = 0 + cfg.BellatrixForkEpoch = 1 + cfg.CapellaForkEpoch = 2 + cfg.DenebForkEpoch = 3 + cfg.ElectraForkEpoch = 4 + params.OverrideBeaconConfig(cfg) + + t.Run("altair", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestAltair() + + slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + stateRoot, err := l.State.HashTreeRoot(l.Ctx) + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot: l.State, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com/", nil) + request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientBootstrap(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientBootstrapResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp) + require.NoError(t, err) + var respHeader structs.LightClientHeader + err = json.Unmarshal(resp.Data.Header, &respHeader) + require.NoError(t, err) + require.Equal(t, "altair", resp.Version) + + blockHeader, err := l.Block.Header() + require.NoError(t, err) + require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) + require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) + + require.NotNil(t, resp.Data.CurrentSyncCommittee) + require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) + }) + t.Run("bellatrix", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestBellatrix() + + slot := primitives.Slot(params.BeaconConfig().BellatrixForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + blockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot: l.State, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com/", nil) + request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientBootstrap(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientBootstrapResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp) + require.NoError(t, err) + var respHeader structs.LightClientHeader + err = json.Unmarshal(resp.Data.Header, &respHeader) + require.NoError(t, err) + require.Equal(t, "altair", resp.Version) + + blockHeader, err := l.Block.Header() + require.NoError(t, err) + require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) + require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) + + require.NotNil(t, resp.Data.CurrentSyncCommittee) + require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) + }) + t.Run("capella", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestCapella(false) // result is same for true and false + + slot := primitives.Slot(params.BeaconConfig().CapellaForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + blockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot: l.State, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com/", nil) + request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientBootstrap(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientBootstrapResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp) + require.NoError(t, err) + var respHeader structs.LightClientHeader + err = json.Unmarshal(resp.Data.Header, &respHeader) + require.NoError(t, err) + require.Equal(t, "capella", resp.Version) + + blockHeader, err := l.Block.Header() + require.NoError(t, err) + require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) + require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) + + require.NotNil(t, resp.Data.CurrentSyncCommittee) + require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) + }) + t.Run("deneb", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestDeneb(false) // result is same for true and false + + slot := primitives.Slot(params.BeaconConfig().DenebForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + blockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot: l.State, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com/", nil) + request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientBootstrap(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientBootstrapResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp) + require.NoError(t, err) + var respHeader structs.LightClientHeader + err = json.Unmarshal(resp.Data.Header, &respHeader) + require.NoError(t, err) + require.Equal(t, "deneb", resp.Version) + + blockHeader, err := l.Block.Header() + require.NoError(t, err) + require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) + require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) + + require.NotNil(t, resp.Data.CurrentSyncCommittee) + require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) + }) + t.Run("electra", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestElectra(false) // result is same for true and false + + slot := primitives.Slot(params.BeaconConfig().ElectraForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + blockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot: l.State, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com/", nil) + request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientBootstrap(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientBootstrapResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp) + require.NoError(t, err) + var respHeader structs.LightClientHeader + err = json.Unmarshal(resp.Data.Header, &respHeader) + require.NoError(t, err) + require.Equal(t, "electra", resp.Version) + + blockHeader, err := l.Block.Header() + require.NoError(t, err) + require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) + require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) + + require.NotNil(t, resp.Data.CurrentSyncCommittee) + require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) + }) +} + +// GetLightClientByRange tests + +func TestLightClientHandler_GetLightClientUpdatesByRangeAltair(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 0 + params.OverrideBeaconConfig(config) + + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + err = st.SetSlot(slot) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) + + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + err = db.SaveLightClientUpdate(ctx, updatePeriod, update) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) + startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} - s.GetLightClientBootstrap(writer, request) + s.GetLightClientUpdatesByRange(writer, request) + require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientBootstrapResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.Header, &respHeader) + require.Equal(t, 1, len(resp.Updates)) + require.Equal(t, "altair", resp.Updates[0].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) require.NoError(t, err) - require.Equal(t, "altair", resp.Version) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) +} + +func TestLightClientHandler_GetLightClientUpdatesByRangeCapella(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 0 + config.CapellaForkEpoch = 1 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - blockHeader, err := l.Block.Header() + st, err := util.NewBeaconStateCapella() + require.NoError(t, err) + err = st.SetSlot(slot) require.NoError(t, err) - require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) - require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) - require.NotNil(t, resp.Data.CurrentSyncCommittee) - require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) -} + db := dbtesting.SetupDB(t) + + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) -func TestLightClientHandler_GetLightClientBootstrap_Bellatrix(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestBellatrix() + update, err := createUpdate(t, version.Capella) + require.NoError(t, err) - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) + err = db.SaveLightClientUpdate(ctx, updatePeriod, update) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) + startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} - s.GetLightClientBootstrap(writer, request) + s.GetLightClientUpdatesByRange(writer, request) + require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientBootstrapResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.Header, &respHeader) + require.Equal(t, 1, len(resp.Updates)) + require.Equal(t, "capella", resp.Updates[0].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) require.NoError(t, err) - require.Equal(t, "bellatrix", resp.Version) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) +} + +func TestLightClientHandler_GetLightClientUpdatesByRangeDeneb(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 0 + config.CapellaForkEpoch = 1 + config.DenebForkEpoch = 2 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - blockHeader, err := l.Block.Header() + st, err := util.NewBeaconStateDeneb() + require.NoError(t, err) + err = st.SetSlot(slot) require.NoError(t, err) - require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) - require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) - require.NotNil(t, resp.Data.CurrentSyncCommittee) - require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) -} + db := dbtesting.SetupDB(t) -func TestLightClientHandler_GetLightClientBootstrap_Capella(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestCapella(false) // result is same for true and false + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) + update, err := createUpdate(t, version.Deneb) + require.NoError(t, err) + err = db.SaveLightClientUpdate(ctx, updatePeriod, update) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) + startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} - s.GetLightClientBootstrap(writer, request) + s.GetLightClientUpdatesByRange(writer, request) + require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientBootstrapResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.Header, &respHeader) + require.Equal(t, 1, len(resp.Updates)) + require.Equal(t, "deneb", resp.Updates[0].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) require.NoError(t, err) - require.Equal(t, "capella", resp.Version) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) +} + +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleAltair(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 0 + config.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - blockHeader, err := l.Block.Header() + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 periods + err = st.SetSlot(headSlot) require.NoError(t, err) - require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) - require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) - require.NotNil(t, resp.Data.CurrentSyncCommittee) - require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) -} + db := dbtesting.SetupDB(t) -func TestLightClientHandler_GetLightClientBootstrap_Deneb(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestDeneb(false) // result is same for true and false + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) - require.NoError(t, err) + updates := make([]interfaces.LightClientUpdate, 0) + for i := 1; i <= 2; i++ { + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + updates = append(updates, update) + } + + for _, update := range updates { + err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) + require.NoError(t, err) + updatePeriod++ + } - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} - s.GetLightClientBootstrap(writer, request) + s.GetLightClientUpdatesByRange(writer, request) + require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientBootstrapResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.Header, &respHeader) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - require.Equal(t, "deneb", resp.Version) + require.Equal(t, 2, len(resp.Updates)) + for i, update := range updates { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } +} + +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleCapella(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 0 + config.CapellaForkEpoch = 1 + config.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - blockHeader, err := l.Block.Header() + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 periods + err = st.SetSlot(headSlot) require.NoError(t, err) - require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) - require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) - require.NotNil(t, resp.Data.CurrentSyncCommittee) - require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) -} + db := dbtesting.SetupDB(t) -func TestLightClientHandler_GetLightClientBootstrap_Electra(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestElectra(false) // result is same for true and false + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) - require.NoError(t, err) + updates := make([]interfaces.LightClientUpdate, 0) + for i := 0; i < 2; i++ { + update, err := createUpdate(t, version.Capella) + require.NoError(t, err) + updates = append(updates, update) + } - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} + for _, update := range updates { + err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) + require.NoError(t, err) + updatePeriod++ + } + + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} - s.GetLightClientBootstrap(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientBootstrapResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.Header, &respHeader) - require.NoError(t, err) - require.Equal(t, "electra", resp.Version) + s.GetLightClientUpdatesByRange(writer, request) - blockHeader, err := l.Block.Header() + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) - require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) - - require.NotNil(t, resp.Data.CurrentSyncCommittee) - require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) + require.Equal(t, 2, len(resp.Updates)) + for i, update := range updates { + require.Equal(t, "capella", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } } -func TestLightClientHandler_GetLightClientUpdatesByRangeAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleDeneb(t *testing.T) { helpers.ClearCache() ctx := context.Background() + params.SetupTestConfigCleanup(t) config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + config.AltairForkEpoch = 0 + config.CapellaForkEpoch = 1 + config.DenebForkEpoch = 2 + config.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateAltair() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) + headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) + err = st.SetSlot(headSlot) require.NoError(t, err) - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) + db := dbtesting.SetupDB(t) - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + updates := make([]interfaces.LightClientUpdate, 0) + for i := 0; i < 2; i++ { + update, err := createUpdate(t, version.Deneb) + require.NoError(t, err) + updates = append(updates, update) + } - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) + for _, update := range updates { + err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) + require.NoError(t, err) + updatePeriod++ + } + mockChainService := &mock.ChainService{State: st} + s := &Server{ + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) + s.GetLightClientUpdatesByRange(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, 2, len(resp.Updates)) + for i, update := range updates { + require.Equal(t, "deneb", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } +} + +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleForksAltairCapella(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 0 + config.CapellaForkEpoch = 1 + config.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(config) + slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + slotAltair := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = st.SetSlot(slot) + headSlot := slotCapella.Add(1) + err = st.SetSlot(headSlot) require.NoError(t, err) - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) + db := dbtesting.SetupDB(t) - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] + updates := make([]interfaces.LightClientUpdate, 2) - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } + updatePeriod := slotAltair.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - signedBlock, err := blocks.NewSignedBeaconBlock(block) + updates[0], err = createUpdate(t, version.Altair) require.NoError(t, err) - h, err := signedBlock.Header() + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0]) require.NoError(t, err) - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) + updatePeriod = slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) + updates[1], err = createUpdate(t, version.Capella) require.NoError(t, err) - root, err := block.Block.HashTreeRoot() + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1]) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) + startPeriod := 0 + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -322,1098 +632,386 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeAltair(t *testing.T) { var resp structs.LightClientUpdatesByRangeResponse err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) + require.Equal(t, 2, len(resp.Updates)) + for i, update := range updates { + if i < 1 { + require.Equal(t, "altair", resp.Updates[i].Version) + } else { + require.Equal(t, "capella", resp.Updates[i].Version) + } + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } } -func TestLightClientHandler_GetLightClientUpdatesByRangeCapella(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleForksCapellaDeneb(t *testing.T) { helpers.ClearCache() ctx := context.Background() + params.SetupTestConfigCleanup(t) config := params.BeaconConfig() - slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) + config.AltairForkEpoch = 0 + config.CapellaForkEpoch = 1 + config.DenebForkEpoch = 2 + config.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(config) + slotDeneb := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - signedParent, err := blocks.NewSignedBeaconBlock(parent) + st, err := util.NewBeaconStateAltair() require.NoError(t, err) - - parentHeader, err := signedParent.Header() + headSlot := slotDeneb.Add(1) + err = st.SetSlot(headSlot) require.NoError(t, err) - attestedHeader := parentHeader.Header - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) + db := dbtesting.SetupDB(t) - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + updates := make([]interfaces.LightClientUpdate, 2) - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) + updatePeriod := slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - parentRoot, err := signedParent.Block().HashTreeRoot() + updates[0], err = createUpdate(t, version.Capella) require.NoError(t, err) - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0]) require.NoError(t, err) - h, err := signedBlock.Header() - require.NoError(t, err) + updatePeriod = slotDeneb.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeaderCapella - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "capella", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -func TestLightClientHandler_GetLightClientUpdatesByRangeDeneb(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateDeneb() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockDeneb() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateDeneb() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockDeneb() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeaderDeneb - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "deneb", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountAltair(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - 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, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountCapella(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeaderCapella - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - 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), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountDeneb(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateDeneb() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockDeneb() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateDeneb() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockDeneb() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeaderDeneb - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - 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, "deneb", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -// TODO - check for not having any blocks from the min period, and startPeriod being too early -func TestLightClientHandler_GetLightClientUpdatesByRange_TooEarlyPeriodAltair(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := 1 // very early period before Altair fork - count := 1 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -// TODO - same as above -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigCountAltair(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := 1 // very early period before Altair fork - count := 10 // This is big count as we only have one period in test case. - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -func TestLightClientHandler_GetLightClientUpdatesByRange_BeforeAltair(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Sub(1) - - attestedState, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) + updates[1], err = createUpdate(t, version.Deneb) require.NoError(t, err) - root, err := block.Block.HashTreeRoot() + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1]) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 1 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + startPeriod := 1 + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} s.GetLightClientUpdatesByRange(writer, request) - require.Equal(t, http.StatusNotFound, writer.Code) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) + require.NoError(t, err) + require.Equal(t, 2, len(resp.Updates)) + for i, update := range updates { + if i < 1 { + require.Equal(t, "capella", resp.Updates[i].Version) + } else { + require.Equal(t, "deneb", resp.Updates[i].Version) + } + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } } -func TestLightClientHandler_GetLightClientFinalityUpdateAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeCountBiggerThanLimit(t *testing.T) { helpers.ClearCache() ctx := context.Background() + params.SetupTestConfigCleanup(t) config := params.BeaconConfig() + config.AltairForkEpoch = 0 + config.EpochsPerSyncCommitteePeriod = 1 + config.MaxRequestLightClientUpdates = 2 + params.OverrideBeaconConfig(config) slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateAltair() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) + headSlot := slot.Add(4 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 4 periods + err = st.SetSlot(headSlot) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ - Epoch: config.AltairForkEpoch - 10, - Root: make([]byte, 32), - })) + db := dbtesting.SetupDB(t) - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) + updates := make([]interfaces.LightClientUpdate, 3) - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + for i := 0; i < 3; i++ { - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) + updates[i], err = createUpdate(t, version.Altair) + require.NoError(t, err) - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[i]) + require.NoError(t, err) + + updatePeriod++ + } + + mockChainService := &mock.ChainService{State: st} + s := &Server{ + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := 0 + url := fmt.Sprintf("http://foo.com/?count=4&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientUpdatesByRange(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, 2, len(resp.Updates)) + for i, update := range updates { + if i < 2 { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } + } +} + +func TestLightClientHandler_GetLightClientUpdatesByRangeCountBiggerThanMax(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 0 + config.EpochsPerSyncCommitteePeriod = 1 + config.MaxRequestLightClientUpdates = 2 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = st.SetSlot(slot) + headSlot := slot.Add(4 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 4 periods + err = st.SetSlot(headSlot) require.NoError(t, err) - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) + db := dbtesting.SetupDB(t) - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] + updates := make([]interfaces.LightClientUpdate, 3) - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 3; i++ { + updates[i], err = createUpdate(t, version.Altair) + require.NoError(t, err) + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[i]) + require.NoError(t, err) + + updatePeriod++ } - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) + mockChainService := &mock.ChainService{State: st} + s := &Server{ + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := 0 + url := fmt.Sprintf("http://foo.com/?count=10&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - h, err := signedBlock.Header() + s.GetLightClientUpdatesByRange(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, 2, len(resp.Updates)) + for i, update := range updates { + if i < 2 { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } + } +} - err = st.SetLatestBlockHeader(h.Header) +func TestLightClientHandler_GetLightClientUpdatesByRangeStartPeriodBeforeAltair(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 1 + config.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + + st, err := util.NewBeaconStateAltair() require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) + headSlot := slot.Add(1) + err = st.SetSlot(headSlot) require.NoError(t, err) - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) + db := dbtesting.SetupDB(t) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + update, err := createUpdate(t, version.Altair) require.NoError(t, err) - root, err := block.Block.HashTreeRoot() + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ - root: true, - }} + mockChainService := &mock.ChainService{State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - request := httptest.NewRequest("GET", "http://foo.com", nil) + startPeriod := 0 + url := fmt.Sprintf("http://foo.com/?count=2&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} - s.GetLightClientFinalityUpdate(writer, request) + s.GetLightClientUpdatesByRange(writer, request) require.Equal(t, http.StatusOK, writer.Code) - var resp *structs.LightClientUpdateResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) + require.Equal(t, 1, len(resp.Updates)) + + require.Equal(t, "altair", resp.Updates[0].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) require.NoError(t, err) - require.Equal(t, "altair", resp.Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp.Data) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) + } -func TestLightClientHandler_GetLightClientFinalityUpdateCapella(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMissingUpdates(t *testing.T) { helpers.ClearCache() ctx := context.Background() + params.SetupTestConfigCleanup(t) config := params.BeaconConfig() - slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + config.AltairForkEpoch = 0 + config.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(config) + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateCapella() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) + headSlot := slot.Add(4 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 4 periods + err = st.SetSlot(headSlot) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ - Epoch: config.AltairForkEpoch - 10, - Root: make([]byte, 32), - })) + t.Run("missing update in the middle", func(t *testing.T) { + db := dbtesting.SetupDB(t) - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) + updates := make([]interfaces.LightClientUpdate, 3) - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + for i := 0; i < 3; i++ { + if i == 1 { // skip this update + updatePeriod++ + continue + } - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) + updates[i], err = createUpdate(t, version.Altair) + require.NoError(t, err) - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[i]) + require.NoError(t, err) - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) + updatePeriod++ + } - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) + mockChainService := &mock.ChainService{State: st} + s := &Server{ + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := 0 + url := fmt.Sprintf("http://foo.com/?count=10&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] + s.GetLightClientUpdatesByRange(writer, request) - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) + require.NoError(t, err) + require.Equal(t, 1, len(resp.Updates)) + require.Equal(t, "altair", resp.Updates[0].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(updates[0]) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) + }) - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) + t.Run("missing update at the beginning", func(t *testing.T) { + db := dbtesting.SetupDB(t) - h, err := signedBlock.Header() - require.NoError(t, err) + updates := make([]interfaces.LightClientUpdate, 3) - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) + for i := 0; i < 3; i++ { + if i == 0 { // skip this update + updatePeriod++ + continue + } - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) + updates[i], err = createUpdate(t, version.Altair) + require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ - root: true, - }} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - request := httptest.NewRequest("GET", "http://foo.com", nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[i]) + require.NoError(t, err) - s.GetLightClientFinalityUpdate(writer, request) + updatePeriod++ + } + + mockChainService := &mock.ChainService{State: st} + s := &Server{ + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := 0 + url := fmt.Sprintf("http://foo.com/?count=10&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientUpdatesByRange(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) + require.NoError(t, err) + require.Equal(t, 0, len(resp.Updates)) + }) - require.Equal(t, http.StatusOK, writer.Code) - var resp *structs.LightClientUpdateResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, "capella", resp.Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp.Data) } -func TestLightClientHandler_GetLightClientFinalityUpdateDeneb(t *testing.T) { +func TestLightClientHandler_GetLightClientFinalityUpdate(t *testing.T) { helpers.ClearCache() ctx := context.Background() config := params.BeaconConfig() - slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateDeneb() + attestedState, err := util.NewBeaconStateAltair() require.NoError(t, err) err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ Epoch: config.AltairForkEpoch - 10, Root: make([]byte, 32), })) - parent := util.NewBeaconBlockDeneb() + parent := util.NewBeaconBlockAltair() parent.Block.Slot = slot.Sub(1) signedParent, err := blocks.NewSignedBeaconBlock(parent) @@ -1433,7 +1031,7 @@ func TestLightClientHandler_GetLightClientFinalityUpdateDeneb(t *testing.T) { signedParent, err = blocks.NewSignedBeaconBlock(parent) require.NoError(t, err) - st, err := util.NewBeaconStateDeneb() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) err = st.SetSlot(slot) require.NoError(t, err) @@ -1441,7 +1039,7 @@ func TestLightClientHandler_GetLightClientFinalityUpdateDeneb(t *testing.T) { parentRoot, err := signedParent.Block().HashTreeRoot() require.NoError(t, err) - block := util.NewBeaconBlockDeneb() + block := util.NewBeaconBlockAltair() block.Block.Slot = slot block.Block.ParentRoot = parentRoot[:] @@ -1481,13 +1079,15 @@ func TestLightClientHandler_GetLightClientFinalityUpdateDeneb(t *testing.T) { mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ root: true, }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} s := &Server{ Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ slot.Sub(1): attestedState, slot: st, }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, } request := httptest.NewRequest("GET", "http://foo.com", nil) writer := httptest.NewRecorder() @@ -1499,10 +1099,10 @@ func TestLightClientHandler_GetLightClientFinalityUpdateDeneb(t *testing.T) { var resp *structs.LightClientUpdateResponse err = json.Unmarshal(writer.Body.Bytes(), &resp) require.NoError(t, err) - var respHeader structs.LightClientHeaderDeneb + var respHeader structs.LightClientHeader err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) require.NoError(t, err) - require.Equal(t, "deneb", resp.Version) + require.Equal(t, "altair", resp.Version) require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) require.NotNil(t, resp.Data) } @@ -1518,7 +1118,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdateAltair(t *testing.T) { err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ Epoch: config.AltairForkEpoch - 10, Root: make([]byte, 32), })) @@ -1591,13 +1191,15 @@ func TestLightClientHandler_GetLightClientOptimisticUpdateAltair(t *testing.T) { mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ root: true, }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} s := &Server{ Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ slot.Sub(1): attestedState, slot: st, }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, } request := httptest.NewRequest("GET", "http://foo.com", nil) writer := httptest.NewRecorder() @@ -1628,7 +1230,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdateCapella(t *testing.T) err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ Epoch: config.AltairForkEpoch - 10, Root: make([]byte, 32), })) @@ -1701,13 +1303,15 @@ func TestLightClientHandler_GetLightClientOptimisticUpdateCapella(t *testing.T) mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ root: true, }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} s := &Server{ Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ slot.Sub(1): attestedState, slot: st, }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, } request := httptest.NewRequest("GET", "http://foo.com", nil) writer := httptest.NewRecorder() @@ -1738,7 +1342,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdateDeneb(t *testing.T) { err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ Epoch: config.AltairForkEpoch - 10, Root: make([]byte, 32), })) @@ -1811,13 +1415,15 @@ func TestLightClientHandler_GetLightClientOptimisticUpdateDeneb(t *testing.T) { mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ root: true, }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} s := &Server{ Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ slot.Sub(1): attestedState, slot: st, }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, } request := httptest.NewRequest("GET", "http://foo.com", nil) writer := httptest.NewRecorder() @@ -1848,7 +1454,7 @@ func TestLightClientHandler_GetLightClientEventBlock(t *testing.T) { err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ Epoch: config.AltairForkEpoch - 10, Root: make([]byte, 32), })) @@ -1921,13 +1527,15 @@ func TestLightClientHandler_GetLightClientEventBlock(t *testing.T) { mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ root: true, }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} s := &Server{ Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ slot.Sub(1): attestedState, slot: st, }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, } minSignaturesRequired := uint64(100) @@ -1952,7 +1560,7 @@ func TestLightClientHandler_GetLightClientEventBlock_NeedFetchParent(t *testing. err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ Epoch: config.AltairForkEpoch - 10, Root: make([]byte, 32), })) @@ -2028,13 +1636,15 @@ func TestLightClientHandler_GetLightClientEventBlock_NeedFetchParent(t *testing. mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ root: true, }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} s := &Server{ Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ slot.Sub(1): attestedState, slot: st, }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, } minSignaturesRequired := uint64(100) @@ -2047,3 +1657,142 @@ func TestLightClientHandler_GetLightClientEventBlock_NeedFetchParent(t *testing. require.Equal(t, true, syncAggregate.SyncCommitteeBits.Count() >= minSignaturesRequired) require.Equal(t, slot-1, eventBlock.Block().Slot()) } + +func createUpdate(t *testing.T, v int) (interfaces.LightClientUpdate, error) { + config := params.BeaconConfig() + var slot primitives.Slot + var header interfaces.LightClientHeader + var st state.BeaconState + var err error + + sampleRoot := make([]byte, 32) + for i := 0; i < 32; i++ { + sampleRoot[i] = byte(i) + } + + sampleExecutionBranch := make([][]byte, fieldparams.ExecutionBranchDepth) + for i := 0; i < 4; i++ { + sampleExecutionBranch[i] = make([]byte, 32) + for j := 0; j < 32; j++ { + sampleExecutionBranch[i][j] = byte(i + j) + } + } + + switch v { + case version.Altair: + slot = primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, + }, + }) + require.NoError(t, err) + st, err = util.NewBeaconStateAltair() + require.NoError(t, err) + case version.Capella: + slot = primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderCapella{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + }, + ExecutionBranch: sampleExecutionBranch, + }) + require.NoError(t, err) + st, err = util.NewBeaconStateCapella() + require.NoError(t, err) + case version.Deneb: + slot = primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, + }, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + }, + ExecutionBranch: sampleExecutionBranch, + }) + require.NoError(t, err) + st, err = util.NewBeaconStateDeneb() + require.NoError(t, err) + case version.Electra: + slot = primitives.Slot(config.ElectraForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderDeneb{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: primitives.ValidatorIndex(rand.Int()), + ParentRoot: sampleRoot, + StateRoot: sampleRoot, + BodyRoot: sampleRoot, + }, + Execution: &enginev1.ExecutionPayloadHeaderElectra{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, fieldparams.LogsBloomLength), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + }, + ExecutionBranch: sampleExecutionBranch, + }) + require.NoError(t, err) + st, err = util.NewBeaconStateElectra() + require.NoError(t, err) + default: + return nil, fmt.Errorf("unsupported version %s", version.String(v)) + } + + update, err := lightclient.CreateDefaultLightClientUpdate(slot, st) + require.NoError(t, err) + update.SetSignatureSlot(slot - 1) + syncCommitteeBits := make([]byte, 64) + syncCommitteeSignature := make([]byte, 96) + update.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: syncCommitteeBits, + SyncCommitteeSignature: syncCommitteeSignature, + }) + + require.NoError(t, update.SetAttestedHeader(header)) + require.NoError(t, update.SetFinalizedHeader(header)) + + return update, nil +} diff --git a/beacon-chain/rpc/eth/light-client/helpers.go b/beacon-chain/rpc/eth/light-client/helpers.go index ce989a7d5672..779bbd447dbd 100644 --- a/beacon-chain/rpc/eth/light-client/helpers.go +++ b/beacon-chain/rpc/eth/light-client/helpers.go @@ -2,274 +2,25 @@ package lightclient import ( "context" - "encoding/json" - "fmt" - "reflect" - "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v5/proto/migration" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" - lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" - "github.com/prysmaticlabs/prysm/v5/runtime/version" - - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prysmaticlabs/prysm/v5/api/server/structs" + lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" - fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" - "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" - v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" - "github.com/prysmaticlabs/prysm/v5/time/slots" ) -func createLightClientBootstrap(ctx context.Context, state state.BeaconState, blk interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) { - switch blk.Version() { - case version.Phase0: - return nil, fmt.Errorf("light client bootstrap is not supported for phase0") - case version.Altair, version.Bellatrix: - return createLightClientBootstrapAltair(ctx, state, blk) - case version.Capella: - return createLightClientBootstrapCapella(ctx, state, blk) - case version.Deneb, version.Electra: - return createLightClientBootstrapDeneb(ctx, state, blk) - } - return nil, fmt.Errorf("unsupported block version %s", version.String(blk.Version())) -} - -func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) { - // assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH - if slots.ToEpoch(state.Slot()) < params.BeaconConfig().AltairForkEpoch { - return nil, fmt.Errorf("light client bootstrap is not supported before Altair, invalid slot %d", state.Slot()) - } - - // assert state.slot == state.latest_block_header.slot - latestBlockHeader := state.LatestBlockHeader() - if state.Slot() != latestBlockHeader.Slot { - return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot) - } - - // header.state_root = hash_tree_root(state) - stateRoot, err := state.HashTreeRoot(ctx) - if err != nil { - return nil, errors.Wrap(err, "could not get state root") - } - latestBlockHeader.StateRoot = stateRoot[:] - - // assert hash_tree_root(header) == hash_tree_root(block.message) - latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot() - if err != nil { - return nil, errors.Wrap(err, "could not get latest block header root") - } - beaconBlockRoot, err := block.Block().HashTreeRoot() - if err != nil { - return nil, errors.Wrap(err, "could not get block root") - } - if latestBlockHeaderRoot != beaconBlockRoot { - return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot) - } - - lightClientHeaderContainer, err := lightclient.BlockToLightClientHeader(block) - if err != nil { - return nil, errors.Wrap(err, "could not convert block to light client header") - } - lightClientHeader := lightClientHeaderContainer.GetHeaderAltair() - - apiLightClientHeader := &structs.LightClientHeader{ - Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)), - } - - headerJSON, err := json.Marshal(apiLightClientHeader) - if err != nil { - return nil, errors.Wrap(err, "could not convert header to raw message") - } - currentSyncCommittee, err := state.CurrentSyncCommittee() - if err != nil { - return nil, errors.Wrap(err, "could not get current sync committee") - } - currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx) - if err != nil { - return nil, errors.Wrap(err, "could not get current sync committee proof") - } - - branch := make([]string, fieldparams.SyncCommitteeBranchDepth) - for i, proof := range currentSyncCommitteeProof { - branch[i] = hexutil.Encode(proof) - } - result := &structs.LightClientBootstrap{ - Header: headerJSON, - CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee), - CurrentSyncCommitteeBranch: branch, - } - - return result, nil -} - -func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) { - // assert compute_epoch_at_slot(state.slot) >= CAPELLA_FORK_EPOCH - if slots.ToEpoch(state.Slot()) < params.BeaconConfig().CapellaForkEpoch { - return nil, fmt.Errorf("creating Capella light client bootstrap is not supported before Capella, invalid slot %d", state.Slot()) - } - - // assert state.slot == state.latest_block_header.slot - latestBlockHeader := state.LatestBlockHeader() - if state.Slot() != latestBlockHeader.Slot { - return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot) - } - - // header.state_root = hash_tree_root(state) - stateRoot, err := state.HashTreeRoot(ctx) - if err != nil { - return nil, errors.Wrap(err, "could not get state root") - } - latestBlockHeader.StateRoot = stateRoot[:] - - // assert hash_tree_root(header) == hash_tree_root(block.message) - latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot() - if err != nil { - return nil, errors.Wrap(err, "could not get latest block header root") - } - beaconBlockRoot, err := block.Block().HashTreeRoot() - if err != nil { - return nil, errors.Wrap(err, "could not get block root") - } - if latestBlockHeaderRoot != beaconBlockRoot { - return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot) - } - - lightClientHeaderContainer, err := lightclient.BlockToLightClientHeader(block) - if err != nil { - return nil, errors.Wrap(err, "could not convert block to light client header") - } - lightClientHeader := lightClientHeaderContainer.GetHeaderCapella() - - apiLightClientHeader := &structs.LightClientHeader{ - Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)), - } - - headerJSON, err := json.Marshal(apiLightClientHeader) - if err != nil { - return nil, errors.Wrap(err, "could not convert header to raw message") - } - currentSyncCommittee, err := state.CurrentSyncCommittee() - if err != nil { - return nil, errors.Wrap(err, "could not get current sync committee") - } - currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx) - if err != nil { - return nil, errors.Wrap(err, "could not get current sync committee proof") - } - - branch := make([]string, fieldparams.SyncCommitteeBranchDepth) - for i, proof := range currentSyncCommitteeProof { - branch[i] = hexutil.Encode(proof) - } - result := &structs.LightClientBootstrap{ - Header: headerJSON, - CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee), - CurrentSyncCommitteeBranch: branch, - } - - return result, nil -} - -func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) { - // assert compute_epoch_at_slot(state.slot) >= DENEB_FORK_EPOCH - if slots.ToEpoch(state.Slot()) < params.BeaconConfig().DenebForkEpoch { - return nil, fmt.Errorf("creating Deneb light client bootstrap is not supported before Deneb, invalid slot %d", state.Slot()) - } - - // assert state.slot == state.latest_block_header.slot - latestBlockHeader := state.LatestBlockHeader() - if state.Slot() != latestBlockHeader.Slot { - return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot) - } - - // header.state_root = hash_tree_root(state) - stateRoot, err := state.HashTreeRoot(ctx) - if err != nil { - return nil, errors.Wrap(err, "could not get state root") - } - latestBlockHeader.StateRoot = stateRoot[:] - - // assert hash_tree_root(header) == hash_tree_root(block.message) - latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot() - if err != nil { - return nil, errors.Wrap(err, "could not get latest block header root") - } - beaconBlockRoot, err := block.Block().HashTreeRoot() - if err != nil { - return nil, errors.Wrap(err, "could not get block root") - } - if latestBlockHeaderRoot != beaconBlockRoot { - return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot) - } - - lightClientHeaderContainer, err := lightclient.BlockToLightClientHeader(block) - if err != nil { - return nil, errors.Wrap(err, "could not convert block to light client header") - } - lightClientHeader := lightClientHeaderContainer.GetHeaderDeneb() - - apiLightClientHeader := &structs.LightClientHeader{ - Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)), - } - - headerJSON, err := json.Marshal(apiLightClientHeader) - if err != nil { - return nil, errors.Wrap(err, "could not convert header to raw message") - } - currentSyncCommittee, err := state.CurrentSyncCommittee() - if err != nil { - return nil, errors.Wrap(err, "could not get current sync committee") - } - currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx) - if err != nil { - return nil, errors.Wrap(err, "could not get current sync committee proof") - } - var branch []string - switch block.Version() { - case version.Deneb: - branch = make([]string, fieldparams.SyncCommitteeBranchDepth) - case version.Electra: - branch = make([]string, fieldparams.SyncCommitteeBranchDepthElectra) - } - for i, proof := range currentSyncCommitteeProof { - branch[i] = hexutil.Encode(proof) - } - result := &structs.LightClientBootstrap{ - Header: headerJSON, - CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee), - CurrentSyncCommitteeBranch: branch, - } - - return result, nil -} - -func newLightClientUpdateFromBeaconState( - ctx context.Context, - state state.BeaconState, - block interfaces.ReadOnlySignedBeaconBlock, - attestedState state.BeaconState, - attestedBlock interfaces.ReadOnlySignedBeaconBlock, - finalizedBlock interfaces.ReadOnlySignedBeaconBlock, -) (*structs.LightClientUpdate, error) { - result, err := lightclient.NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, finalizedBlock) - if err != nil { - return nil, err - } - - return structs.LightClientUpdateFromConsensus(result) -} - func newLightClientFinalityUpdateFromBeaconState( ctx context.Context, + currentSlot primitives.Slot, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock, attestedState state.BeaconState, attestedBlock interfaces.ReadOnlySignedBeaconBlock, finalizedBlock interfaces.ReadOnlySignedBeaconBlock, ) (*structs.LightClientFinalityUpdate, error) { - result, err := lightclient.NewLightClientFinalityUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, finalizedBlock) + result, err := lightclient.NewLightClientFinalityUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock, finalizedBlock) if err != nil { return nil, err } @@ -279,94 +30,16 @@ func newLightClientFinalityUpdateFromBeaconState( func newLightClientOptimisticUpdateFromBeaconState( ctx context.Context, + currentSlot primitives.Slot, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock, attestedState state.BeaconState, attestedBlock interfaces.ReadOnlySignedBeaconBlock, ) (*structs.LightClientOptimisticUpdate, error) { - result, err := lightclient.NewLightClientOptimisticUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock) + result, err := lightclient.NewLightClientOptimisticUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock) if err != nil { return nil, err } return structs.LightClientOptimisticUpdateFromConsensus(result) } - -func IsSyncCommitteeUpdate(update *v2.LightClientUpdate) bool { - nextSyncCommitteeBranch := make([][]byte, fieldparams.SyncCommitteeBranchDepth) - return !reflect.DeepEqual(update.NextSyncCommitteeBranch, nextSyncCommitteeBranch) -} - -func IsFinalityUpdate(update *v2.LightClientUpdate) bool { - finalityBranch := make([][]byte, lightclient.FinalityBranchNumOfLeaves) - return !reflect.DeepEqual(update.FinalityBranch, finalityBranch) -} - -func IsBetterUpdate(newUpdate, oldUpdate *v2.LightClientUpdate) (bool, error) { - maxActiveParticipants := newUpdate.SyncAggregate.SyncCommitteeBits.Len() - newNumActiveParticipants := newUpdate.SyncAggregate.SyncCommitteeBits.Count() - oldNumActiveParticipants := oldUpdate.SyncAggregate.SyncCommitteeBits.Count() - newHasSupermajority := newNumActiveParticipants*3 >= maxActiveParticipants*2 - oldHasSupermajority := oldNumActiveParticipants*3 >= maxActiveParticipants*2 - - if newHasSupermajority != oldHasSupermajority { - return newHasSupermajority, nil - } - if !newHasSupermajority && newNumActiveParticipants != oldNumActiveParticipants { - return newNumActiveParticipants > oldNumActiveParticipants, nil - } - - newUpdateAttestedHeaderBeacon, err := newUpdate.AttestedHeader.GetBeacon() - if err != nil { - return false, errors.Wrap(err, "could not get attested header beacon") - } - oldUpdateAttestedHeaderBeacon, err := oldUpdate.AttestedHeader.GetBeacon() - if err != nil { - return false, errors.Wrap(err, "could not get attested header beacon") - } - - // Compare presence of relevant sync committee - newHasRelevantSyncCommittee := IsSyncCommitteeUpdate(newUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.SignatureSlot))) - oldHasRelevantSyncCommittee := IsSyncCommitteeUpdate(oldUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.SignatureSlot))) - - if newHasRelevantSyncCommittee != oldHasRelevantSyncCommittee { - return newHasRelevantSyncCommittee, nil - } - - // Compare indication of any finality - newHasFinality := IsFinalityUpdate(newUpdate) - oldHasFinality := IsFinalityUpdate(oldUpdate) - if newHasFinality != oldHasFinality { - return newHasFinality, nil - } - - newUpdateFinalizedHeaderBeacon, err := newUpdate.FinalizedHeader.GetBeacon() - if err != nil { - return false, errors.Wrap(err, "could not get finalized header beacon") - } - oldUpdateFinalizedHeaderBeacon, err := oldUpdate.FinalizedHeader.GetBeacon() - if err != nil { - return false, errors.Wrap(err, "could not get finalized header beacon") - } - - // Compare sync committee finality - if newHasFinality { - newHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateFinalizedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateAttestedHeaderBeacon.Slot)) - oldHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateFinalizedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateAttestedHeaderBeacon.Slot)) - - if newHasSyncCommitteeFinality != oldHasSyncCommitteeFinality { - return newHasSyncCommitteeFinality, nil - } - } - - // Tiebreaker 1: Sync committee participation beyond supermajority - if newNumActiveParticipants != oldNumActiveParticipants { - return newNumActiveParticipants > oldNumActiveParticipants, nil - } - - // Tiebreaker 2: Prefer older data (fewer changes to best) - if newUpdateAttestedHeaderBeacon.Slot != oldUpdateAttestedHeaderBeacon.Slot { - return newUpdateAttestedHeaderBeacon.Slot < oldUpdateAttestedHeaderBeacon.Slot, nil - } - return newUpdate.SignatureSlot < oldUpdate.SignatureSlot, nil -} diff --git a/beacon-chain/rpc/eth/light-client/helpers_test.go b/beacon-chain/rpc/eth/light-client/helpers_test.go index 9b27204fc22e..6d60fca8185a 100644 --- a/beacon-chain/rpc/eth/light-client/helpers_test.go +++ b/beacon-chain/rpc/eth/light-client/helpers_test.go @@ -1,616 +1,680 @@ package lightclient import ( + "strings" "testing" lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" - fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" - ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" + "github.com/prysmaticlabs/prysm/v5/config/params" + light_client "github.com/prysmaticlabs/prysm/v5/consensus-types/light-client" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/assert" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" ) // When the update has relevant sync committee func createNonEmptySyncCommitteeBranch() [][]byte { res := make([][]byte, fieldparams.SyncCommitteeBranchDepth) - res[0] = []byte("xyz") + res[0] = []byte(strings.Repeat("x", 32)) + for i := 1; i < len(res); i++ { + res[i] = make([]byte, fieldparams.RootLength) + } return res } // When the update has finality func createNonEmptyFinalityBranch() [][]byte { - res := make([][]byte, lightclient.FinalityBranchNumOfLeaves) - res[0] = []byte("xyz") + res := make([][]byte, fieldparams.FinalityBranchDepth) + res[0] = []byte(strings.Repeat("x", 32)) + for i := 1; i < fieldparams.FinalityBranchDepth; i++ { + res[i] = make([]byte, 32) + } return res } func TestIsBetterUpdate(t *testing.T) { - testCases := []struct { - name string - oldUpdate *ethpbv2.LightClientUpdate - newUpdate *ethpbv2.LightClientUpdate - expectedResult bool - }{ - { - name: "new has supermajority but old doesn't", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b11111100, 0b1}, // [0,0,1,1,1,1,1,1] - }, - }, - expectedResult: true, - }, - { - name: "old has supermajority but new doesn't", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b11111100, 0b1}, // [0,0,1,1,1,1,1,1] - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] - }, - }, - expectedResult: false, - }, - { - name: "new doesn't have supermajority and newNumActiveParticipants is greater than oldNumActiveParticipants", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] - }, - }, - expectedResult: true, - }, - { - name: "new doesn't have supermajority and newNumActiveParticipants is lesser than oldNumActiveParticipants", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - }, - expectedResult: false, - }, - { - name: "new has relevant sync committee but old doesn't", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: make([][]byte, fieldparams.SyncCommitteeBranchDepth), - SignatureSlot: 9999, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000001, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 1000000, - }, - expectedResult: true, - }, - { - name: "old has relevant sync committee but new doesn't", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000001, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 1000000, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: make([][]byte, fieldparams.SyncCommitteeBranchDepth), - SignatureSlot: 9999, - }, - expectedResult: false, - }, - { - name: "new has finality but old doesn't", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: make([][]byte, lightclient.FinalityBranchNumOfLeaves), - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - }, - expectedResult: true, - }, - { - name: "old has finality but new doesn't", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: make([][]byte, lightclient.FinalityBranchNumOfLeaves), - }, - expectedResult: false, - }, - { - name: "new has finality and sync committee finality both but old doesn't have sync committee finality", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 999999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 999999, - }}, - }, - }, - }, - expectedResult: true, - }, - { - name: "new has finality but doesn't have sync committee finality and old has sync committee finality", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 999999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 999999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - expectedResult: false, - }, - { - name: "new has more active participants than old", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - expectedResult: true, - }, - { - name: "new has less active participants than old", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - expectedResult: false, - }, - { - name: "new's attested header's slot is lesser than old's attested header's slot", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 999999, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - expectedResult: true, - }, - { - name: "new's attested header's slot is greater than old's attested header's slot", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 999999, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - expectedResult: false, - }, - { - name: "none of the above conditions are met and new signature's slot is less than old signature's slot", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9998, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - expectedResult: true, - }, - { - name: "none of the above conditions are met and new signature's slot is greater than old signature's slot", - oldUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9998, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - newUpdate: ðpbv2.LightClientUpdate{ - SyncAggregate: ðpbv1.SyncAggregate{ - SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] - }, - AttestedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 1000000, - }}, - }, - }, - NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(), - SignatureSlot: 9999, - FinalityBranch: createNonEmptyFinalityBranch(), - FinalizedHeader: ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ - HeaderAltair: ðpbv2.LightClientHeader{Beacon: ðpbv1.BeaconBlockHeader{ - Slot: 9999, - }}, - }, - }, - }, - expectedResult: false, - }, - } + config := params.BeaconConfig() + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + + t.Run("new has supermajority but old doesn't", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result, err := IsBetterUpdate(testCase.newUpdate, testCase.oldUpdate) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedResult, result) + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] }) - } + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b11111100, 0b1}, // [0,0,1,1,1,1,1,1] + }) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("old has supermajority but new doesn't", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b11111100, 0b1}, // [0,0,1,1,1,1,1,1] + }) + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] + }) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + + t.Run("new doesn't have supermajority and newNumActiveParticipants is greater than oldNumActiveParticipants", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] + }) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("new doesn't have supermajority and newNumActiveParticipants is lesser than oldNumActiveParticipants", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] + }) + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + + t.Run("new has relevant sync committee but old doesn't", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9999) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000001, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(1000000) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("old has relevant sync committee but new doesn't", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000001, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(1000000) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9999) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + + t.Run("new has finality but old doesn't", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9999) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9999) + err = newUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("old has finality but new doesn't", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9999) + err = oldUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9999) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + + t.Run("new has finality and sync committee finality both but old doesn't have sync committee finality", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + err = oldUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9999) + oldFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetFinalizedHeader(oldFinalizedHeader) + assert.NoError(t, err) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(999999) + err = newUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + newFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 999999, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetFinalizedHeader(newFinalizedHeader) + assert.NoError(t, err) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("new has finality but doesn't have sync committee finality and old has sync committee finality", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + err = oldUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(999999) + oldFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 999999, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetFinalizedHeader(oldFinalizedHeader) + assert.NoError(t, err) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9999) + err = newUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + newFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetFinalizedHeader(newFinalizedHeader) + assert.NoError(t, err) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + + t.Run("new has more active participants than old", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0] + }) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("new has less active participants than old", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0] + }) + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + + t.Run("new's attested header's slot is lesser than old's attested header's slot", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + err = oldUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9999) + oldFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetFinalizedHeader(oldFinalizedHeader) + assert.NoError(t, err) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 999999, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9999) + err = newUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + newFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetFinalizedHeader(newFinalizedHeader) + assert.NoError(t, err) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("new's attested header's slot is greater than old's attested header's slot", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 999999, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + err = oldUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9999) + oldFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetFinalizedHeader(oldFinalizedHeader) + assert.NoError(t, err) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9999) + err = newUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + newFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetFinalizedHeader(newFinalizedHeader) + assert.NoError(t, err) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + + t.Run("none of the above conditions are met and new signature's slot is less than old signature's slot", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + err = oldUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9999) + oldFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetFinalizedHeader(oldFinalizedHeader) + assert.NoError(t, err) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9998) + err = newUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + newFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetFinalizedHeader(newFinalizedHeader) + assert.NoError(t, err) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + + t.Run("none of the above conditions are met and new signature's slot is greater than old signature's slot", func(t *testing.T) { + oldUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(1), st) + assert.NoError(t, err) + newUpdate, err := lightclient.CreateDefaultLightClientUpdate(primitives.Slot(config.AltairForkEpoch*primitives.Epoch(config.SlotsPerEpoch)).Add(2), st) + assert.NoError(t, err) + + oldUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + oldAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetAttestedHeader(oldAttestedHeader) + assert.NoError(t, err) + err = oldUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + err = oldUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + oldUpdate.SetSignatureSlot(9998) + oldFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = oldUpdate.SetFinalizedHeader(oldFinalizedHeader) + assert.NoError(t, err) + + newUpdate.SetSyncAggregate(&pb.SyncAggregate{ + SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0] + }) + newAttestedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 1000000, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetAttestedHeader(newAttestedHeader) + assert.NoError(t, err) + err = newUpdate.SetNextSyncCommitteeBranch(createNonEmptySyncCommitteeBranch()) + assert.NoError(t, err) + newUpdate.SetSignatureSlot(9999) + err = newUpdate.SetFinalityBranch(createNonEmptyFinalityBranch()) + assert.NoError(t, err) + newFinalizedHeader, err := light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{ + Beacon: &pb.BeaconBlockHeader{ + Slot: 9999, + }, + }) + assert.NoError(t, err) + err = newUpdate.SetFinalizedHeader(newFinalizedHeader) + assert.NoError(t, err) + + result, err := lightclient.IsBetterUpdate(newUpdate, oldUpdate) + assert.NoError(t, err) + assert.Equal(t, false, result) + }) } diff --git a/beacon-chain/rpc/eth/light-client/server.go b/beacon-chain/rpc/eth/light-client/server.go index e0773a306405..84b061379fc1 100644 --- a/beacon-chain/rpc/eth/light-client/server.go +++ b/beacon-chain/rpc/eth/light-client/server.go @@ -7,8 +7,9 @@ import ( ) type Server struct { - Blocker lookup.Blocker - Stater lookup.Stater - HeadFetcher blockchain.HeadFetcher - BeaconDB db.HeadAccessDatabase + Blocker lookup.Blocker + Stater lookup.Stater + HeadFetcher blockchain.HeadFetcher + ChainInfoFetcher blockchain.ChainInfoFetcher + BeaconDB db.HeadAccessDatabase } diff --git a/consensus-types/blocks/proofs.go b/consensus-types/blocks/proofs.go index 19d399a9c9b4..4bd114af9f1e 100644 --- a/consensus-types/blocks/proofs.go +++ b/consensus-types/blocks/proofs.go @@ -240,23 +240,13 @@ func PayloadProof(ctx context.Context, block interfaces.ReadOnlyBeaconBlock) ([] return nil, errors.New("failed to cast block body") } - blockBodyFieldRoots, err := ComputeBlockBodyFieldRoots(ctx, blockBody) + fieldRoots, err := ComputeBlockBodyFieldRoots(ctx, blockBody) if err != nil { return nil, err } - blockBodyFieldRootsTrie := stateutil.Merkleize(blockBodyFieldRoots) - blockBodyProof := trie.ProofFromMerkleLayers(blockBodyFieldRootsTrie, payloadFieldIndex) + fieldRootsTrie := stateutil.Merkleize(fieldRoots) + proof := trie.ProofFromMerkleLayers(fieldRootsTrie, payloadFieldIndex) - beaconBlockFieldRoots, err := ComputeBlockFieldRoots(ctx, block) - if err != nil { - return nil, err - } - - beaconBlockFieldRootsTrie := stateutil.Merkleize(beaconBlockFieldRoots) - beaconBlockProof := trie.ProofFromMerkleLayers(beaconBlockFieldRootsTrie, bodyFieldIndex) - - finalProof := append(blockBodyProof, beaconBlockProof...) - - return finalProof, nil + return proof, nil } diff --git a/consensus-types/interfaces/light_client.go b/consensus-types/interfaces/light_client.go index 078cc135ecdb..867ceb281a0b 100644 --- a/consensus-types/interfaces/light_client.go +++ b/consensus-types/interfaces/light_client.go @@ -27,9 +27,12 @@ type LightClientBootstrap interface { ssz.Marshaler Version() int Header() LightClientHeader + SetHeader(header LightClientHeader) error CurrentSyncCommittee() *pb.SyncCommittee + SetCurrentSyncCommittee(sc *pb.SyncCommittee) error CurrentSyncCommitteeBranch() (LightClientSyncCommitteeBranch, error) CurrentSyncCommitteeBranchElectra() (LightClientSyncCommitteeBranchElectra, error) + SetCurrentSyncCommitteeBranch(branch [][]byte) error } type LightClientUpdate interface { @@ -56,6 +59,7 @@ type LightClientUpdate interface { type LightClientFinalityUpdate interface { ssz.Marshaler + ssz.Unmarshaler Proto() proto.Message Version() int AttestedHeader() LightClientHeader @@ -68,6 +72,7 @@ type LightClientFinalityUpdate interface { type LightClientOptimisticUpdate interface { ssz.Marshaler + ssz.Unmarshaler Proto() proto.Message Version() int AttestedHeader() LightClientHeader diff --git a/consensus-types/light-client/bootstrap.go b/consensus-types/light-client/bootstrap.go index 1de819266d03..be943d4ddc1f 100644 --- a/consensus-types/light-client/bootstrap.go +++ b/consensus-types/light-client/bootstrap.go @@ -41,10 +41,16 @@ func NewWrappedBootstrapAltair(p *pb.LightClientBootstrapAltair) (interfaces.Lig if p == nil { return nil, consensustypes.ErrNilObjectWrapped } - header, err := NewWrappedHeader(p.Header) - if err != nil { - return nil, err + + var header interfaces.LightClientHeader + var err error + if p.Header != nil { + header, err = NewWrappedHeader(p.Header) + if err != nil { + return nil, err + } } + branch, err := createBranch[interfaces.LightClientSyncCommitteeBranch]( "sync committee", p.CurrentSyncCommitteeBranch, @@ -81,14 +87,42 @@ func (h *bootstrapAltair) Header() interfaces.LightClientHeader { return h.header } +func (h *bootstrapAltair) SetHeader(header interfaces.LightClientHeader) error { + p, ok := (header.Proto()).(*pb.LightClientHeaderAltair) + if !ok { + return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderAltair{}) + } + h.p.Header = p + h.header = header + return nil +} + func (h *bootstrapAltair) CurrentSyncCommittee() *pb.SyncCommittee { return h.p.CurrentSyncCommittee } +func (h *bootstrapAltair) SetCurrentSyncCommittee(sc *pb.SyncCommittee) error { + h.p.CurrentSyncCommittee = sc + return nil +} + func (h *bootstrapAltair) CurrentSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) { return h.currentSyncCommitteeBranch, nil } +func (h *bootstrapAltair) SetCurrentSyncCommitteeBranch(branch [][]byte) error { + if len(branch) != fieldparams.SyncCommitteeBranchDepth { + return fmt.Errorf("branch length %d is not %d", len(branch), fieldparams.SyncCommitteeBranchDepth) + } + newBranch := [fieldparams.SyncCommitteeBranchDepth][32]byte{} + for i, root := range branch { + copy(newBranch[i][:], root) + } + h.currentSyncCommitteeBranch = newBranch + h.p.CurrentSyncCommitteeBranch = branch + return nil +} + func (h *bootstrapAltair) CurrentSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) { return [6][32]byte{}, consensustypes.ErrNotSupported("CurrentSyncCommitteeBranchElectra", version.Altair) } @@ -105,10 +139,16 @@ func NewWrappedBootstrapCapella(p *pb.LightClientBootstrapCapella) (interfaces.L if p == nil { return nil, consensustypes.ErrNilObjectWrapped } - header, err := NewWrappedHeader(p.Header) - if err != nil { - return nil, err + + var header interfaces.LightClientHeader + var err error + if p.Header != nil { + header, err = NewWrappedHeader(p.Header) + if err != nil { + return nil, err + } } + branch, err := createBranch[interfaces.LightClientSyncCommitteeBranch]( "sync committee", p.CurrentSyncCommitteeBranch, @@ -145,14 +185,42 @@ func (h *bootstrapCapella) Header() interfaces.LightClientHeader { return h.header } +func (h *bootstrapCapella) SetHeader(header interfaces.LightClientHeader) error { + p, ok := (header.Proto()).(*pb.LightClientHeaderCapella) + if !ok { + return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderCapella{}) + } + h.p.Header = p + h.header = header + return nil +} + func (h *bootstrapCapella) CurrentSyncCommittee() *pb.SyncCommittee { return h.p.CurrentSyncCommittee } +func (h *bootstrapCapella) SetCurrentSyncCommittee(sc *pb.SyncCommittee) error { + h.p.CurrentSyncCommittee = sc + return nil +} + func (h *bootstrapCapella) CurrentSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) { return h.currentSyncCommitteeBranch, nil } +func (h *bootstrapCapella) SetCurrentSyncCommitteeBranch(branch [][]byte) error { + if len(branch) != fieldparams.SyncCommitteeBranchDepth { + return fmt.Errorf("branch length %d is not %d", len(branch), fieldparams.SyncCommitteeBranchDepth) + } + newBranch := [fieldparams.SyncCommitteeBranchDepth][32]byte{} + for i, root := range branch { + copy(newBranch[i][:], root) + } + h.currentSyncCommitteeBranch = newBranch + h.p.CurrentSyncCommitteeBranch = branch + return nil +} + func (h *bootstrapCapella) CurrentSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) { return [6][32]byte{}, consensustypes.ErrNotSupported("CurrentSyncCommitteeBranchElectra", version.Capella) } @@ -169,10 +237,16 @@ func NewWrappedBootstrapDeneb(p *pb.LightClientBootstrapDeneb) (interfaces.Light if p == nil { return nil, consensustypes.ErrNilObjectWrapped } - header, err := NewWrappedHeader(p.Header) - if err != nil { - return nil, err + + var header interfaces.LightClientHeader + var err error + if p.Header != nil { + header, err = NewWrappedHeader(p.Header) + if err != nil { + return nil, err + } } + branch, err := createBranch[interfaces.LightClientSyncCommitteeBranch]( "sync committee", p.CurrentSyncCommitteeBranch, @@ -209,14 +283,42 @@ func (h *bootstrapDeneb) Header() interfaces.LightClientHeader { return h.header } +func (h *bootstrapDeneb) SetHeader(header interfaces.LightClientHeader) error { + p, ok := (header.Proto()).(*pb.LightClientHeaderDeneb) + if !ok { + return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderDeneb{}) + } + h.p.Header = p + h.header = header + return nil +} + func (h *bootstrapDeneb) CurrentSyncCommittee() *pb.SyncCommittee { return h.p.CurrentSyncCommittee } +func (h *bootstrapDeneb) SetCurrentSyncCommittee(sc *pb.SyncCommittee) error { + h.p.CurrentSyncCommittee = sc + return nil +} + func (h *bootstrapDeneb) CurrentSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) { return h.currentSyncCommitteeBranch, nil } +func (h *bootstrapDeneb) SetCurrentSyncCommitteeBranch(branch [][]byte) error { + if len(branch) != fieldparams.SyncCommitteeBranchDepth { + return fmt.Errorf("branch length %d is not %d", len(branch), fieldparams.SyncCommitteeBranchDepth) + } + newBranch := [fieldparams.SyncCommitteeBranchDepth][32]byte{} + for i, root := range branch { + copy(newBranch[i][:], root) + } + h.currentSyncCommitteeBranch = newBranch + h.p.CurrentSyncCommitteeBranch = branch + return nil +} + func (h *bootstrapDeneb) CurrentSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) { return [6][32]byte{}, consensustypes.ErrNotSupported("CurrentSyncCommitteeBranchElectra", version.Deneb) } @@ -233,10 +335,16 @@ func NewWrappedBootstrapElectra(p *pb.LightClientBootstrapElectra) (interfaces.L if p == nil { return nil, consensustypes.ErrNilObjectWrapped } - header, err := NewWrappedHeader(p.Header) - if err != nil { - return nil, err + + var header interfaces.LightClientHeader + var err error + if p.Header != nil { + header, err = NewWrappedHeader(p.Header) + if err != nil { + return nil, err + } } + branch, err := createBranch[interfaces.LightClientSyncCommitteeBranchElectra]( "sync committee", p.CurrentSyncCommitteeBranch, @@ -273,14 +381,42 @@ func (h *bootstrapElectra) Header() interfaces.LightClientHeader { return h.header } +func (h *bootstrapElectra) SetHeader(header interfaces.LightClientHeader) error { + p, ok := (header.Proto()).(*pb.LightClientHeaderDeneb) + if !ok { + return fmt.Errorf("header type %T is not %T", p, &pb.LightClientHeaderDeneb{}) + } + h.p.Header = p + h.header = header + return nil +} + func (h *bootstrapElectra) CurrentSyncCommittee() *pb.SyncCommittee { return h.p.CurrentSyncCommittee } +func (h *bootstrapElectra) SetCurrentSyncCommittee(sc *pb.SyncCommittee) error { + h.p.CurrentSyncCommittee = sc + return nil +} + func (h *bootstrapElectra) CurrentSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) { return [5][32]byte{}, consensustypes.ErrNotSupported("CurrentSyncCommitteeBranch", version.Electra) } +func (h *bootstrapElectra) SetCurrentSyncCommitteeBranch(branch [][]byte) error { + if len(branch) != fieldparams.SyncCommitteeBranchDepthElectra { + return fmt.Errorf("branch length %d is not %d", len(branch), fieldparams.SyncCommitteeBranchDepthElectra) + } + newBranch := [fieldparams.SyncCommitteeBranchDepthElectra][32]byte{} + for i, root := range branch { + copy(newBranch[i][:], root) + } + h.currentSyncCommitteeBranch = newBranch + h.p.CurrentSyncCommitteeBranch = branch + return nil +} + func (h *bootstrapElectra) CurrentSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) { return h.currentSyncCommitteeBranch, nil } diff --git a/consensus-types/light-client/finality_update.go b/consensus-types/light-client/finality_update.go index 51cbd2408033..465ba2b353af 100644 --- a/consensus-types/light-client/finality_update.go +++ b/consensus-types/light-client/finality_update.go @@ -139,6 +139,23 @@ func (u *finalityUpdateAltair) SizeSSZ() int { return u.p.SizeSSZ() } +func (u *finalityUpdateAltair) UnmarshalSSZ(buf []byte) error { + p := &pb.LightClientFinalityUpdateAltair{} + if err := p.UnmarshalSSZ(buf); err != nil { + return err + } + updateInterface, err := NewWrappedFinalityUpdateAltair(p) + if err != nil { + return err + } + update, ok := updateInterface.(*finalityUpdateAltair) + if !ok { + return fmt.Errorf("unexpected update type %T", updateInterface) + } + *u = *update + return nil +} + func (u *finalityUpdateAltair) Proto() proto.Message { return u.p } @@ -221,6 +238,23 @@ func (u *finalityUpdateCapella) SizeSSZ() int { return u.p.SizeSSZ() } +func (u *finalityUpdateCapella) UnmarshalSSZ(buf []byte) error { + p := &pb.LightClientFinalityUpdateCapella{} + if err := p.UnmarshalSSZ(buf); err != nil { + return err + } + updateInterface, err := NewWrappedFinalityUpdateCapella(p) + if err != nil { + return err + } + update, ok := updateInterface.(*finalityUpdateCapella) + if !ok { + return fmt.Errorf("unexpected update type %T", updateInterface) + } + *u = *update + return nil +} + func (u *finalityUpdateCapella) Proto() proto.Message { return u.p } @@ -303,6 +337,23 @@ func (u *finalityUpdateDeneb) SizeSSZ() int { return u.p.SizeSSZ() } +func (u *finalityUpdateDeneb) UnmarshalSSZ(buf []byte) error { + p := &pb.LightClientFinalityUpdateDeneb{} + if err := p.UnmarshalSSZ(buf); err != nil { + return err + } + updateInterface, err := NewWrappedFinalityUpdateDeneb(p) + if err != nil { + return err + } + update, ok := updateInterface.(*finalityUpdateDeneb) + if !ok { + return fmt.Errorf("unexpected update type %T", updateInterface) + } + *u = *update + return nil +} + func (u *finalityUpdateDeneb) Proto() proto.Message { return u.p } @@ -386,6 +437,23 @@ func (u *finalityUpdateElectra) SizeSSZ() int { return u.p.SizeSSZ() } +func (u *finalityUpdateElectra) UnmarshalSSZ(buf []byte) error { + p := &pb.LightClientFinalityUpdateElectra{} + if err := p.UnmarshalSSZ(buf); err != nil { + return err + } + updateInterface, err := NewWrappedFinalityUpdateElectra(p) + if err != nil { + return err + } + update, ok := updateInterface.(*finalityUpdateElectra) + if !ok { + return fmt.Errorf("unexpected update type %T", updateInterface) + } + *u = *update + return nil +} + func (u *finalityUpdateElectra) Proto() proto.Message { return u.p } diff --git a/consensus-types/light-client/optimistic_update.go b/consensus-types/light-client/optimistic_update.go index dd15ba02267e..6e88774b6251 100644 --- a/consensus-types/light-client/optimistic_update.go +++ b/consensus-types/light-client/optimistic_update.go @@ -104,6 +104,23 @@ func (u *optimisticUpdateAltair) SizeSSZ() int { return u.p.SizeSSZ() } +func (u *optimisticUpdateAltair) UnmarshalSSZ(buf []byte) error { + p := &pb.LightClientOptimisticUpdateAltair{} + if err := p.UnmarshalSSZ(buf); err != nil { + return err + } + updateInterface, err := NewWrappedOptimisticUpdateAltair(p) + if err != nil { + return err + } + update, ok := updateInterface.(*optimisticUpdateAltair) + if !ok { + return fmt.Errorf("unexpected update type %T", updateInterface) + } + *u = *update + return nil +} + func (u *optimisticUpdateAltair) Proto() proto.Message { return u.p } @@ -158,6 +175,23 @@ func (u *optimisticUpdateCapella) SizeSSZ() int { return u.p.SizeSSZ() } +func (u *optimisticUpdateCapella) UnmarshalSSZ(buf []byte) error { + p := &pb.LightClientOptimisticUpdateCapella{} + if err := p.UnmarshalSSZ(buf); err != nil { + return err + } + updateInterface, err := NewWrappedOptimisticUpdateCapella(p) + if err != nil { + return err + } + update, ok := updateInterface.(*optimisticUpdateCapella) + if !ok { + return fmt.Errorf("unexpected update type %T", updateInterface) + } + *u = *update + return nil +} + func (u *optimisticUpdateCapella) Proto() proto.Message { return u.p } @@ -212,6 +246,23 @@ func (u *optimisticUpdateDeneb) SizeSSZ() int { return u.p.SizeSSZ() } +func (u *optimisticUpdateDeneb) UnmarshalSSZ(buf []byte) error { + p := &pb.LightClientOptimisticUpdateDeneb{} + if err := p.UnmarshalSSZ(buf); err != nil { + return err + } + updateInterface, err := NewWrappedOptimisticUpdateDeneb(p) + if err != nil { + return err + } + update, ok := updateInterface.(*optimisticUpdateDeneb) + if !ok { + return fmt.Errorf("unexpected update type %T", updateInterface) + } + *u = *update + return nil +} + func (u *optimisticUpdateDeneb) Proto() proto.Message { return u.p } diff --git a/consensus-types/light-client/update.go b/consensus-types/light-client/update.go index aff6a953d336..84e7afbecb12 100644 --- a/consensus-types/light-client/update.go +++ b/consensus-types/light-client/update.go @@ -168,6 +168,10 @@ func (u *updateAltair) FinalityBranch() (interfaces.LightClientFinalityBranch, e return u.finalityBranch, nil } +func (u *updateAltair) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { + return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", version.Altair) +} + func (u *updateAltair) SetFinalityBranch(branch [][]byte) error { b, err := createBranch[interfaces.LightClientFinalityBranch]("finality", branch, fieldparams.FinalityBranchDepth) if err != nil { @@ -178,10 +182,6 @@ func (u *updateAltair) SetFinalityBranch(branch [][]byte) error { return nil } -func (u *updateAltair) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { - return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", version.Altair) -} - func (u *updateAltair) SyncAggregate() *pb.SyncAggregate { return u.p.SyncAggregate } @@ -335,6 +335,10 @@ func (u *updateCapella) FinalityBranch() (interfaces.LightClientFinalityBranch, return u.finalityBranch, nil } +func (u *updateCapella) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { + return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", u.Version()) +} + func (u *updateCapella) SetFinalityBranch(branch [][]byte) error { b, err := createBranch[interfaces.LightClientFinalityBranch]("finality", branch, fieldparams.FinalityBranchDepth) if err != nil { @@ -345,10 +349,6 @@ func (u *updateCapella) SetFinalityBranch(branch [][]byte) error { return nil } -func (u *updateCapella) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { - return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", u.Version()) -} - func (u *updateCapella) SyncAggregate() *pb.SyncAggregate { return u.p.SyncAggregate } @@ -502,6 +502,10 @@ func (u *updateDeneb) FinalityBranch() (interfaces.LightClientFinalityBranch, er return u.finalityBranch, nil } +func (u *updateDeneb) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { + return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", u.Version()) +} + func (u *updateDeneb) SetFinalityBranch(branch [][]byte) error { b, err := createBranch[interfaces.LightClientFinalityBranch]("finality", branch, fieldparams.FinalityBranchDepth) if err != nil { @@ -512,10 +516,6 @@ func (u *updateDeneb) SetFinalityBranch(branch [][]byte) error { return nil } -func (u *updateDeneb) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { - return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", u.Version()) -} - func (u *updateDeneb) SyncAggregate() *pb.SyncAggregate { return u.p.SyncAggregate } @@ -670,6 +670,10 @@ func (u *updateElectra) FinalityBranch() (interfaces.LightClientFinalityBranch, return interfaces.LightClientFinalityBranch{}, consensustypes.ErrNotSupported("FinalityBranch", u.Version()) } +func (u *updateElectra) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { + return u.finalityBranch, nil +} + func (u *updateElectra) SetFinalityBranch(branch [][]byte) error { b, err := createBranch[interfaces.LightClientFinalityBranchElectra]("finality", branch, fieldparams.FinalityBranchDepthElectra) if err != nil { @@ -680,10 +684,6 @@ func (u *updateElectra) SetFinalityBranch(branch [][]byte) error { return nil } -func (u *updateElectra) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) { - return u.finalityBranch, nil -} - func (u *updateElectra) SyncAggregate() *pb.SyncAggregate { return u.p.SyncAggregate } diff --git a/testing/util/lightclient.go b/testing/util/lightclient.go index a79e7f8f72d9..0cbb0542f9ab 100644 --- a/testing/util/lightclient.go +++ b/testing/util/lightclient.go @@ -14,9 +14,8 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/ssz" v11 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" - ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" - ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" ) @@ -47,14 +46,14 @@ func (l *TestLightClient) SetupTestCapella(blinded bool) *TestLightClient { finalizedBlock, err := blocks.NewSignedBeaconBlock(NewBeaconBlockCapella()) require.NoError(l.T, err) - finalizedBlock.SetSlot(1) + finalizedBlock.SetSlot(primitives.Slot(params.BeaconConfig().CapellaForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch))) finalizedHeader, err := finalizedBlock.Header() require.NoError(l.T, err) finalizedRoot, err := finalizedHeader.Header.HashTreeRoot() require.NoError(l.T, err) require.NoError(l.T, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ - Epoch: params.BeaconConfig().CapellaForkEpoch - 10, + Epoch: params.BeaconConfig().CapellaForkEpoch, Root: finalizedRoot[:], })) @@ -262,25 +261,33 @@ func (l *TestLightClient) SetupTestCapellaFinalizedBlockAltair(blinded bool) *Te func (l *TestLightClient) SetupTestAltair() *TestLightClient { ctx := context.Background() - slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + slot := primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch)).Add(1) attestedState, err := NewBeaconStateAltair() require.NoError(l.T, err) err = attestedState.SetSlot(slot) require.NoError(l.T, err) - finalizedBlock, err := blocks.NewSignedBeaconBlock(NewBeaconBlockAltair()) + finalizedState, err := NewBeaconStateAltair() require.NoError(l.T, err) - finalizedBlock.SetSlot(1) - finalizedHeader, err := finalizedBlock.Header() + err = finalizedState.SetSlot(1) + require.NoError(l.T, err) + finalizedStateRoot, err := finalizedState.HashTreeRoot(ctx) + require.NoError(l.T, err) + SignedFinalizedBlock, err := blocks.NewSignedBeaconBlock(NewBeaconBlockAltair()) + require.NoError(l.T, err) + SignedFinalizedBlock.SetSlot(1) + SignedFinalizedBlock.SetStateRoot(finalizedStateRoot[:]) + finalizedHeader, err := SignedFinalizedBlock.Header() require.NoError(l.T, err) finalizedRoot, err := finalizedHeader.Header.HashTreeRoot() require.NoError(l.T, err) - require.NoError(l.T, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ + finalizedCheckpoint := ðpb.Checkpoint{ Epoch: params.BeaconConfig().AltairForkEpoch - 10, Root: finalizedRoot[:], - })) + } + require.NoError(l.T, attestedState.SetFinalizedCheckpoint(finalizedCheckpoint)) parent := NewBeaconBlockAltair() parent.Block.Slot = slot @@ -338,7 +345,7 @@ func (l *TestLightClient) SetupTestAltair() *TestLightClient { l.AttestedState = attestedState l.Block = signedBlock l.Ctx = ctx - l.FinalizedBlock = finalizedBlock + l.FinalizedBlock = SignedFinalizedBlock l.AttestedBlock = signedParent return l @@ -441,14 +448,14 @@ func (l *TestLightClient) SetupTestDeneb(blinded bool) *TestLightClient { finalizedBlock, err := blocks.NewSignedBeaconBlock(NewBeaconBlockDeneb()) require.NoError(l.T, err) - finalizedBlock.SetSlot(1) + finalizedBlock.SetSlot(primitives.Slot(params.BeaconConfig().DenebForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch))) finalizedHeader, err := finalizedBlock.Header() require.NoError(l.T, err) finalizedRoot, err := finalizedHeader.Header.HashTreeRoot() require.NoError(l.T, err) require.NoError(l.T, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ - Epoch: params.BeaconConfig().DenebForkEpoch - 10, + Epoch: params.BeaconConfig().DenebForkEpoch, Root: finalizedRoot[:], })) @@ -665,14 +672,14 @@ func (l *TestLightClient) SetupTestDenebFinalizedBlockCapella(blinded bool) *Tes finalizedBlock, err := blocks.NewSignedBeaconBlock(NewBeaconBlockCapella()) require.NoError(l.T, err) - finalizedBlock.SetSlot(1) + finalizedBlock.SetSlot(primitives.Slot(params.BeaconConfig().DenebForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Sub(15)) finalizedHeader, err := finalizedBlock.Header() require.NoError(l.T, err) finalizedRoot, err := finalizedHeader.Header.HashTreeRoot() require.NoError(l.T, err) require.NoError(l.T, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ - Epoch: params.BeaconConfig().CapellaForkEpoch - 10, + Epoch: params.BeaconConfig().DenebForkEpoch - 1, Root: finalizedRoot[:], })) @@ -765,9 +772,8 @@ func (l *TestLightClient) SetupTestDenebFinalizedBlockCapella(blinded bool) *Tes return l } -func (l *TestLightClient) CheckAttestedHeader(container *ethpbv2.LightClientHeaderContainer) { - updateAttestedHeaderBeacon, err := container.GetBeacon() - require.NoError(l.T, err) +func (l *TestLightClient) CheckAttestedHeader(header interfaces.LightClientHeader) { + updateAttestedHeaderBeacon := header.Beacon() testAttestedHeader, err := l.AttestedBlock.Header() require.NoError(l.T, err) require.Equal(l.T, l.AttestedBlock.Block().Slot(), updateAttestedHeaderBeacon.Slot, "Attested block slot is not equal") @@ -820,16 +826,16 @@ func (l *TestLightClient) CheckAttestedHeader(container *ethpbv2.LightClientHead WithdrawalsRoot: withdrawalsRoot, } - updateAttestedHeaderExecution, err := container.GetExecutionHeaderCapella() + updateAttestedHeaderExecution, err := header.Execution() require.NoError(l.T, err) - require.DeepSSZEqual(l.T, execution, updateAttestedHeaderExecution, "Attested Block Execution is not equal") + require.DeepSSZEqual(l.T, execution, updateAttestedHeaderExecution.Proto(), "Attested Block Execution is not equal") executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.AttestedBlock.Block()) require.NoError(l.T, err) - updateAttestedHeaderExecutionBranch, err := container.GetExecutionBranch() + updateAttestedHeaderExecutionBranch, err := header.ExecutionBranch() require.NoError(l.T, err) for i, leaf := range updateAttestedHeaderExecutionBranch { - require.DeepSSZEqual(l.T, executionPayloadProof[i], leaf, "Leaf is not equal") + require.DeepSSZEqual(l.T, executionPayloadProof[i], leaf[:], "Leaf is not equal") } } @@ -874,21 +880,21 @@ func (l *TestLightClient) CheckAttestedHeader(container *ethpbv2.LightClientHead WithdrawalsRoot: withdrawalsRoot, } - updateAttestedHeaderExecution, err := container.GetExecutionHeaderDeneb() + updateAttestedHeaderExecution, err := header.Execution() require.NoError(l.T, err) - require.DeepSSZEqual(l.T, execution, updateAttestedHeaderExecution, "Attested Block Execution is not equal") + require.DeepSSZEqual(l.T, execution, updateAttestedHeaderExecution.Proto(), "Attested Block Execution is not equal") executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.AttestedBlock.Block()) require.NoError(l.T, err) - updateAttestedHeaderExecutionBranch, err := container.GetExecutionBranch() + updateAttestedHeaderExecutionBranch, err := header.ExecutionBranch() require.NoError(l.T, err) for i, leaf := range updateAttestedHeaderExecutionBranch { - require.DeepSSZEqual(l.T, executionPayloadProof[i], leaf, "Leaf is not equal") + require.DeepSSZEqual(l.T, executionPayloadProof[i], leaf[:], "Leaf is not equal") } } } -func (l *TestLightClient) CheckSyncAggregate(sa *ethpbv1.SyncAggregate) { +func (l *TestLightClient) CheckSyncAggregate(sa *pb.SyncAggregate) { syncAggregate, err := l.Block.Block().Body().SyncAggregate() require.NoError(l.T, err) require.DeepSSZEqual(l.T, syncAggregate.SyncCommitteeBits, sa.SyncCommitteeBits, "SyncAggregate bits is not equal")