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 | 212 +- 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 | 189 +- 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 | 818 +++++-- .../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 | 980 +++----- 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 | 160 +- .../rpc/eth/light-client/handlers_test.go | 1975 +++++++---------- 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, 3839 insertions(+), 3448 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b27058a5b7..33055932d0 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 c194d502ee..54ed0b02a9 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 9ed1f50e4e..74af3c41ab 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 50e6281ef9..a0479f6e39 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 result any - var header 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 } - header = &LightClientHeaderCapella{ - Beacon: BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(beacon)), - Execution: execution, - ExecutionBranch: branchToJSON(t.HeaderCapella.ExecutionBranch), + ex, ok := exInterface.Proto().(*enginev1.ExecutionPayloadHeaderCapella) + if !ok { + return nil, fmt.Errorf("execution data is not %T", &enginev1.ExecutionPayloadHeaderCapella{}) } - case *v2.LightClientHeaderContainer_HeaderDeneb: - execution, err := ExecutionPayloadHeaderDenebFromConsensus(t.HeaderDeneb.Execution) + execution, err := ExecutionPayloadHeaderCapellaFromConsensus(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 = &LightClientHeaderCapella{ + Beacon: BeaconBlockHeaderFromConsensus(header.Beacon()), Execution: execution, - ExecutionBranch: branchToJSON(t.HeaderDeneb.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{}) + } + execution, err := ExecutionPayloadHeaderElectraFromConsensus(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[:]), } 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 c12a3d1340..2dc6f11a87 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 b5f92c316e..e850f36ada 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 39a384fff0..80e2146678 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 fe01f582bf..d39e50cb53 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 { - return - } - - config := params.BeaconConfig() - if finalized.Epoch < config.AltairForkEpoch { - return - } - - syncAggregate, err := signed.Block().Body().SyncAggregate() - if err != nil || syncAggregate == nil { - return - } - - // LightClientFinalityUpdate needs super majority - if syncAggregate.SyncCommitteeBits.Count()*3 < config.SyncCommitteeSize*2 { - return - } - - _, err = s.sendLightClientFinalityUpdate(ctx, signed, postState) +// 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("Failed to send light client finality update") + 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 + } + + 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 + } + + 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 + } + + 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 + } + + isNewUpdateBetter, err := lightclient.IsBetterUpdate(update, oldUpdate) + if err != nil { + 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.") } } -// 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 +// 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") + } +} + +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 9b6a99200e..c6b097f2b8 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 6a905d67c7..0d4ec5698d 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 ff8c4d9187..6a55bf2660 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 c984a2f797..998b9845ee 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 c7a264c71b..ecf6b8f71a 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 1173ec3553..54932094b2 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,48 +375,14 @@ 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, - }, - }, 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, - }, - }, nil - case version.Deneb, version.Electra: - denebHeader, err := blockToLightClientHeaderDeneb(context.Background(), block) - if err != nil { - return nil, errors.Wrap(err, "could not get header") - } - return ðpbv2.LightClientHeaderContainer{ - Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ - HeaderDeneb: denebHeader, - }, - }, nil - default: - return nil, fmt.Errorf("unsupported block version %s", version.String(block.Version())) - } -} - -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 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() @@ -348,147 +390,431 @@ func blockToLightClientHeaderAltair(block interfaces.ReadOnlySignedBeaconBlock) return nil, errors.Wrap(err, "could not get body root") } - return ðpbv2.LightClientHeader{ - Beacon: ðpbv1.BeaconBlockHeader{ - Slot: block.Block().Slot(), - ProposerIndex: block.Block().ProposerIndex(), - ParentRoot: parentRoot[:], - StateRoot: stateRoot[:], - BodyRoot: bodyRoot[:], - }, - }, nil + 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[:], + }, + } + } 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[:], + }, + Execution: payloadHeader, + ExecutionBranch: payloadProof, + } + } 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[:], + }, + Execution: payloadHeader, + ExecutionBranch: payloadProof, + } + } + + return light_client.NewWrappedHeader(m) } -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 emptyPayloadProof() [][]byte { + branch := interfaces.LightClientExecutionBranch{} + proof := make([][]byte, len(branch)) + for i, b := range branch { + proof[i] = b[:] } - - 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, err - } - withdrawalsRoot, err := ComputeWithdrawalsRoot(payload) - if err != nil { - return nil, err - } - - 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") - } - - 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") - } - - 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 proof } -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 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 } - - payload, err := block.Block().Body().Execution() + branch, err := update.NextSyncCommitteeBranch() if err != nil { - return nil, errors.Wrap(err, "could not get execution payload") + return false, err } - - transactionsRoot, err := ComputeTransactionsRoot(payload) - if err != nil { - return nil, err - } - withdrawalsRoot, err := ComputeWithdrawalsRoot(payload) - if err != nil { - return nil, err - } - 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") - } - - 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()) - if err != nil { - return nil, errors.Wrap(err, "could not get execution payload proof") - } - - 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") - } - - 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 !reflect.DeepEqual(branch, interfaces.LightClientSyncCommitteeBranch{}), nil +} + +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 + } + + b, err := update.FinalityBranch() + if err != nil { + 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() + + // Compare presence of relevant sync committee + newHasRelevantSyncCommittee, err := HasRelevantSyncCommittee(newUpdate) + if err != nil { + return false, err + } + newHasRelevantSyncCommittee = newHasRelevantSyncCommittee && + (slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.SignatureSlot()))) + oldHasRelevantSyncCommittee, err := HasRelevantSyncCommittee(oldUpdate) + if err != nil { + return false, err + } + oldHasRelevantSyncCommittee = oldHasRelevantSyncCommittee && + (slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.SignatureSlot()))) + + if newHasRelevantSyncCommittee != oldHasRelevantSyncCommittee { + return newHasRelevantSyncCommittee, nil + } + + // Compare indication of any finality + newHasFinality, err := HasFinality(newUpdate) + if err != nil { + 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 newUpdate.SignatureSlot() < oldUpdate.SignatureSlot(), nil +} + +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) + } + + // 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) + } + + bootstrap, err := createDefaultLightClientBootstrap(currentSlot) + if err != nil { + return nil, errors.Wrap(err, "could not create default light client bootstrap") + } + + lightClientHeader, err := BlockToLightClientHeader(ctx, currentSlot, block) + if err != nil { + 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 set header") + } + + currentSyncCommittee, err := state.CurrentSyncCommittee() + if err != nil { + 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 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 eb6ab44170..e26a4d2f7d 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) - 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") } }) }) @@ -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) - 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 @@ -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 993d1fd84c..81929a26a4 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 b75960ef55..082a3816b9 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 732da2fb5b..714bfb1f26 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 63e49e3048..128b17eddf 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 3c7bef3ff5..2e3c8dc9c4 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 dd2df2acac..84702567a3 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, - } - period := uint64(1) - err := db.SaveLightClientUpdate(ctx, period, ðpbv2.LightClientUpdateWithVersion{ - Version: version.Altair, - Data: update, - }) - require.NoError(t, err) +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 - retrievedUpdate, err := db.LightClientUpdate(ctx, period) + 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.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)) + } + + 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_CanSaveRetrieveCapella(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_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, - }, - }, - }, - FinalityBranch: nil, - SyncAggregate: nil, - SignatureSlot: 7, - } - period := uint64(1) - err := db.SaveLightClientUpdate(ctx, period, ðpbv2.LightClientUpdateWithVersion{ - Version: version.Capella, - 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") -} - -func TestStore_LightClientUpdate_CanSaveRetrieveDeneb(t *testing.T) { - 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, + 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("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.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("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) - 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") - } - - // Retrieve the updates from the middle - retrievedUpdates, err = db.LightClientUpdates(ctx, 8, 12) - 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") - - // Retrieve the updates from after the missing period - retrievedUpdates, err = db.LightClientUpdates(ctx, 11, 12) + retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 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.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, 2, 4) + require.NoError(t, err) + 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, 4, 4) + require.NoError(t, err) + 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)), - }, + syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize + pubKeys := make([][]byte, syncCommitteeSize) + for i := uint64(0); i < syncCommitteeSize; i++ { + pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength) + } + nextSyncCommittee := &pb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength), + } + + 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, fieldparams.ExecutionBranchDepth) + for i := 0; i < fieldparams.ExecutionBranchDepth; i++ { + executionBranch[i] = make([]byte, 32) + } + + 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) + } + + var m proto.Message + if currentEpoch < params.BeaconConfig().CapellaForkEpoch { + m = &pb.LightClientUpdateAltair{ + AttestedHeader: &pb.LightClientHeaderAltair{}, + NextSyncCommittee: nextSyncCommittee, + NextSyncCommitteeBranch: nextSyncCommitteeBranch, + FinalityBranch: finalityBranch, } - err := db.SaveLightClientUpdate(ctx, uint64(i), update) - require.NoError(t, err) - } - - 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)), + } 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, + } } - err := db.SaveLightClientUpdate(ctx, uint64(i), update) - require.NoError(t, err) } - 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") - } - 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") - } - -} - -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") - } -} - -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") - } -} - -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") - } -} - -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") - } -} - -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 30d950514c..f6648a8f92 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 0b853cac10..1424ab9375 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 17aba3329e..4021bb2339 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 682fd1f002..6e90360f5a 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 5624b7d5ee..d2a1e918b0 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 6156bb07d0..6caf6a9d6b 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 85b46a07ec..4b668ff2ee 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 245d703eaa..7e0d7a1d7a 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 - } - - if block == nil { - // No valid block found for the period - continue - } - - // Get attested state - attestedRoot := block.Block().ParentRoot() - attestedBlock, err := s.Blocker.Block(ctx, attestedRoot[:]) - if err != nil || attestedBlock == nil { - continue - } - - attestedSlot := attestedBlock.Block().Slot() - attestedState, err := s.Stater.StateBySlot(ctx, attestedSlot) - 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 - } - } - - update, err := newLightClientUpdateFromBeaconState( - ctx, - state, - block, - attestedState, - attestedBlock, - finalizedBlock, - ) - - if err == nil { - updates = append(updates, &structs.LightClientUpdateResponse{ - Version: version.String(attestedState.Version()), - Data: update, - }) - } - } - - if len(updates) == 0 { - httputil.HandleError(w, "no updates found", http.StatusNotFound) + // 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 } + updates := make([]*structs.LightClientUpdateResponse, 0, len(updatesMap)) + + for i := startPeriod; i <= endPeriod; i++ { + update, ok := updatesMap[i] + if !ok { + // Only return the first contiguous range of updates + break + } + + updateJson, err := structs.LightClientUpdateFromConsensus(update) + if err != nil { + httputil.HandleError(w, "Could not convert light client update: "+err.Error(), http.StatusInternalServerError) + return + } + updateResponse := &structs.LightClientUpdateResponse{ + Version: version.String(update.Version()), + Data: updateJson, + } + updates = append(updates, updateResponse) + } 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 342ea679ee..984cd7add0 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,301 +15,271 @@ 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) - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) - require.NoError(t, err) + t.Run("altair", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestAltair() - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + stateRoot, err := l.State.HashTreeRoot(l.Ctx) + require.NoError(t, err) - 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) + 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{} - 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) + 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) - require.NotNil(t, resp.Data.CurrentSyncCommittee) - require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) + 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) + }) } -func TestLightClientHandler_GetLightClientBootstrap_Bellatrix(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestBellatrix() - - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - 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, "bellatrix", 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) -} - -func TestLightClientHandler_GetLightClientBootstrap_Capella(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestCapella(false) // result is same for true and false - - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - 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, "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) -} - -func TestLightClientHandler_GetLightClientBootstrap_Deneb(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestDeneb(false) // result is same for true and false - - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - 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, "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) -} - -func TestLightClientHandler_GetLightClientBootstrap_Electra(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestElectra(false) // result is same for true and false - - slot := l.State.Slot() - stateRoot, err := l.State.HashTreeRoot(l.Ctx) - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - 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, "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) - 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() + db := dbtesting.SetupDB(t) + + 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) - 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} + 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) @@ -322,99 +293,42 @@ 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) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + 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) - 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() + db := dbtesting.SetupDB(t) + + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) + + update, 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, updatePeriod, update) 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} + 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) @@ -428,99 +342,42 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeCapella(t *testing.T) { 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) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + 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) - 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() + db := dbtesting.SetupDB(t) + + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) + + update, err := createUpdate(t, version.Deneb) + require.NoError(t, err) + err = db.SaveLightClientUpdate(ctx, updatePeriod, update) 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} + 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) @@ -534,103 +391,53 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeDeneb(t *testing.T) { 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) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) } -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountAltair(t *testing.T) { +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) - 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) + headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 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[:] + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + 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) } - 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, - }, + for _, update := range updates { + err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) + require.NoError(t, err) + updatePeriod++ } - 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 := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + 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{} @@ -641,103 +448,56 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountAltair( 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) + 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_GetLightClientUpdatesByRange_TooBigInputCountCapella(t *testing.T) { +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) - attestedState, err := util.NewBeaconStateCapella() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) + headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 periods + err = st.SetSlot(headSlot) require.NoError(t, err) - parent := util.NewBeaconBlockCapella() - 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 - - 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) + 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) } - 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, - }, + for _, update := range updates { + err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) + require.NoError(t, err) + updatePeriod++ } - 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 := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + 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{} @@ -748,103 +508,56 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountCapella 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) + 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_GetLightClientUpdatesByRange_TooBigInputCountDeneb(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleDeneb(t *testing.T) { helpers.ClearCache() ctx := context.Background() + params.SetupTestConfigCleanup(t) config := params.BeaconConfig() + 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.NewBeaconStateDeneb() + 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.NewBeaconBlockDeneb() - 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 - - 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) + 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) } - 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, - }, + for _, update := range updates { + err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) + require.NoError(t, err) + updatePeriod++ } - 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 := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + 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{} @@ -855,104 +568,60 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountDeneb(t 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) + 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) + } } -// TODO - check for not having any blocks from the min period, and startPeriod being too early -func TestLightClientHandler_GetLightClientUpdatesByRange_TooEarlyPeriodAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleForksAltairCapella(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) - - 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) + 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() + db := dbtesting.SetupDB(t) + + updates := make([]interfaces.LightClientUpdate, 2) + + updatePeriod := slotAltair.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + updates[0], err = createUpdate(t, version.Altair) 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) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0]) require.NoError(t, err) - h, err := signedBlock.Header() + updatePeriod = slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + updates[1], err = createUpdate(t, version.Capella) require.NoError(t, err) - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1]) 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} + 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 := 1 // very early period before Altair fork - count := 1 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, 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{} @@ -963,104 +632,65 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooEarlyPeriodAltair(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) + } } -// TODO - same as above -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigCountAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleForksCapellaDeneb(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) - - 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) + 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) st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = st.SetSlot(slot) + headSlot := slotDeneb.Add(1) + err = st.SetSlot(headSlot) require.NoError(t, err) - parentRoot, err := signedParent.Block().HashTreeRoot() + db := dbtesting.SetupDB(t) + + updates := make([]interfaces.LightClientUpdate, 2) + + updatePeriod := slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + updates[0], err = createUpdate(t, version.Capella) 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) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0]) require.NoError(t, err) - h, err := signedBlock.Header() + updatePeriod = slotDeneb.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + updates[1], err = createUpdate(t, version.Deneb) require.NoError(t, err) - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1]) 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} + 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 := 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) + 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{} @@ -1071,113 +701,301 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigCountAltair(t *te 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, "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_GetLightClientUpdatesByRange_BeforeAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeCountBiggerThanLimit(t *testing.T) { helpers.ClearCache() ctx := context.Background() + params.SetupTestConfigCleanup(t) config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Sub(1) + 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.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) - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) + db := dbtesting.SetupDB(t) - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + updates := make([]interfaces.LightClientUpdate, 3) - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) + for i := 0; i < 3; i++ { - // 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[i], err = createUpdate(t, version.Altair) + require.NoError(t, err) - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[i]) + 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) + updatePeriod++ } - 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} + 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 := 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.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 < 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_GetLightClientFinalityUpdateAltair(t *testing.T) { +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) + headSlot := slot.Add(4 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 4 periods + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + updates := make([]interfaces.LightClientUpdate, 3) + + 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++ + } + + 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, 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_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) + headSlot := slot.Add(1) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + update, err := createUpdate(t, version.Altair) + require.NoError(t, err) + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update) + require.NoError(t, err) + + mockChainService := &mock.ChainService{State: st} + s := &Server{ + HeadFetcher: mockChainService, + BeaconDB: db, + } + 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.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, 1, len(resp.Updates)) + + require.Equal(t, "altair", resp.Updates[0].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) + +} + +func TestLightClientHandler_GetLightClientUpdatesByRangeMissingUpdates(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) + + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + headSlot := slot.Add(4 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 4 periods + err = st.SetSlot(headSlot) + require.NoError(t, err) + + t.Run("missing update in the middle", func(t *testing.T) { + db := dbtesting.SetupDB(t) + + updates := make([]interfaces.LightClientUpdate, 3) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 3; i++ { + if i == 1 { // skip this update + updatePeriod++ + continue + } + + updates[i], err = createUpdate(t, version.Altair) + require.NoError(t, err) + + 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=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, 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) + }) + + t.Run("missing update at the beginning", func(t *testing.T) { + db := dbtesting.SetupDB(t) + + updates := make([]interfaces.LightClientUpdate, 3) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 3; i++ { + if i == 0 { // skip this update + updatePeriod++ + continue + } + + updates[i], err = createUpdate(t, version.Altair) + require.NoError(t, err) + + 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=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)) + }) + +} + +func TestLightClientHandler_GetLightClientFinalityUpdate(t *testing.T) { helpers.ClearCache() ctx := context.Background() config := params.BeaconConfig() @@ -1188,7 +1006,7 @@ func TestLightClientHandler_GetLightClientFinalityUpdateAltair(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), })) @@ -1261,13 +1079,15 @@ func TestLightClientHandler_GetLightClientFinalityUpdateAltair(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() @@ -1287,226 +1107,6 @@ func TestLightClientHandler_GetLightClientFinalityUpdateAltair(t *testing.T) { require.NotNil(t, resp.Data) } -func TestLightClientHandler_GetLightClientFinalityUpdateCapella(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) - - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ - Epoch: config.AltairForkEpoch - 10, - Root: make([]byte, 32), - })) - - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - 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, 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{} - - s.GetLightClientFinalityUpdate(writer, request) - - 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) { - 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) - - require.NoError(t, attestedState.SetFinalizedCheckpoint(ðpb.Checkpoint{ - Epoch: config.AltairForkEpoch - 10, - Root: make([]byte, 32), - })) - - 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, 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{} - - s.GetLightClientFinalityUpdate(writer, request) - - 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.LightClientHeaderDeneb - err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, "deneb", resp.Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp.Data) -} - func TestLightClientHandler_GetLightClientOptimisticUpdateAltair(t *testing.T) { helpers.ClearCache() ctx := context.Background() @@ -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 ce989a7d56..779bbd447d 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 9b27204fc2..6d60fca818 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) - 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) + 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) + + 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 e0773a3064..84b061379f 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 19d399a9c9..4bd114af9f 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 078cc135ec..867ceb281a 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 1de819266d..be943d4ddc 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 51cbd24080..465ba2b353 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 dd15ba0226..6e88774b62 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 aff6a953d3..84e7afbecb 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 a79e7f8f72..0cbb0542f9 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")