diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bd7d98de5..566568a4d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Add field param placeholder for Electra blob target and max to pass spec tests. - Add EIP-7691: Blob throughput increase. - SSZ files generation: Remove the `// Hash: ...` header. +- DB optimization for saving light client bootstraps (save unique sync committees only). - Trace IDONTWANT Messages in Pubsub. - Add Fulu fork boilerplate. diff --git a/beacon-chain/db/kv/BUILD.bazel b/beacon-chain/db/kv/BUILD.bazel index 714bfb1f26..1e57289ca0 100644 --- a/beacon-chain/db/kv/BUILD.bazel +++ b/beacon-chain/db/kv/BUILD.bazel @@ -41,6 +41,7 @@ go_library( "//beacon-chain/state/genesis:go_default_library", "//beacon-chain/state/state-native:go_default_library", "//config/features: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", diff --git a/beacon-chain/db/kv/kv.go b/beacon-chain/db/kv/kv.go index 128b17eddf..684eac0759 100644 --- a/beacon-chain/db/kv/kv.go +++ b/beacon-chain/db/kv/kv.go @@ -109,6 +109,7 @@ var Buckets = [][]byte{ stateValidatorsBucket, lightClientUpdatesBucket, lightClientBootstrapBucket, + lightClientSyncCommitteeBucket, // Indices buckets. blockSlotIndicesBucket, stateSlotIndicesBucket, diff --git a/beacon-chain/db/kv/lightclient.go b/beacon-chain/db/kv/lightclient.go index 2e3c8dc9c4..cc1655c18c 100644 --- a/beacon-chain/db/kv/lightclient.go +++ b/beacon-chain/db/kv/lightclient.go @@ -7,6 +7,8 @@ import ( "github.com/golang/snappy" "github.com/pkg/errors" + 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/encoding/bytesutil" @@ -35,9 +37,35 @@ func (s *Store) SaveLightClientBootstrap(ctx context.Context, blockRoot []byte, _, span := trace.StartSpan(ctx, "BeaconDB.SaveLightClientBootstrap") defer span.End() + bootstrapCopy, err := light_client.NewWrappedBootstrap(proto.Clone(bootstrap.Proto())) + if err != nil { + return errors.Wrap(err, "could not clone light client bootstrap") + } + syncCommitteeHash, err := bootstrapCopy.CurrentSyncCommittee().HashTreeRoot() + if err != nil { + return errors.Wrap(err, "could not hash current sync committee") + } + return s.db.Update(func(tx *bolt.Tx) error { + syncCommitteeBucket := tx.Bucket(lightClientSyncCommitteeBucket) + syncCommitteeAlreadyExists := syncCommitteeBucket.Get(syncCommitteeHash[:]) != nil + if !syncCommitteeAlreadyExists { + enc, err := bootstrapCopy.CurrentSyncCommittee().MarshalSSZ() + if err != nil { + return errors.Wrap(err, "could not marshal current sync committee") + } + if err := syncCommitteeBucket.Put(syncCommitteeHash[:], enc); err != nil { + return errors.Wrap(err, "could not save current sync committee") + } + } + + err = bootstrapCopy.SetCurrentSyncCommittee(createEmptySyncCommittee()) + if err != nil { + return errors.Wrap(err, "could not set current sync committee to zero while saving") + } + bkt := tx.Bucket(lightClientBootstrapBucket) - enc, err := encodeLightClientBootstrap(bootstrap) + enc, err := encodeLightClientBootstrap(bootstrapCopy, syncCommitteeHash) if err != nil { return err } @@ -50,20 +78,49 @@ func (s *Store) LightClientBootstrap(ctx context.Context, blockRoot []byte) (int defer span.End() var bootstrap interfaces.LightClientBootstrap + var syncCommitteeHash []byte err := s.db.View(func(tx *bolt.Tx) error { bkt := tx.Bucket(lightClientBootstrapBucket) + syncCommitteeBucket := tx.Bucket(lightClientSyncCommitteeBucket) enc := bkt.Get(blockRoot) if enc == nil { return nil } var err error - bootstrap, err = decodeLightClientBootstrap(enc) + bootstrap, syncCommitteeHash, err = decodeLightClientBootstrap(enc) + if err != nil { + return errors.Wrap(err, "could not decode light client bootstrap") + } + var syncCommitteeBytes = syncCommitteeBucket.Get(syncCommitteeHash) + if syncCommitteeBytes == nil { + return errors.New("sync committee not found") + } + syncCommittee := ðpb.SyncCommittee{} + if err := syncCommittee.UnmarshalSSZ(syncCommitteeBytes); err != nil { + return errors.Wrap(err, "could not unmarshal sync committee") + } + err = bootstrap.SetCurrentSyncCommittee(syncCommittee) + if err != nil { + return errors.Wrap(err, "could not set current sync committee while retrieving") + } return err }) return bootstrap, err } -func encodeLightClientBootstrap(bootstrap interfaces.LightClientBootstrap) ([]byte, error) { +func createEmptySyncCommittee() *ethpb.SyncCommittee { + syncCom := make([][]byte, params.BeaconConfig().SyncCommitteeSize) + for i := 0; uint64(i) < params.BeaconConfig().SyncCommitteeSize; i++ { + syncCom[i] = make([]byte, fieldparams.BLSPubkeyLength) + } + + return ðpb.SyncCommittee{ + Pubkeys: syncCom, + AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength), + } +} + +func encodeLightClientBootstrap(bootstrap interfaces.LightClientBootstrap, syncCommitteeHash [32]byte) ([]byte, error) { key, err := keyForLightClientUpdate(bootstrap.Version()) if err != nil { return nil, err @@ -72,48 +129,56 @@ func encodeLightClientBootstrap(bootstrap interfaces.LightClientBootstrap) ([]by if err != nil { return nil, errors.Wrap(err, "could not marshal light client bootstrap") } - fullEnc := make([]byte, len(key)+len(enc)) + fullEnc := make([]byte, len(key)+32+len(enc)) copy(fullEnc, key) - copy(fullEnc[len(key):], enc) - return snappy.Encode(nil, fullEnc), nil + copy(fullEnc[len(key):len(key)+32], syncCommitteeHash[:]) + copy(fullEnc[len(key)+32:], enc) + compressedEnc := snappy.Encode(nil, fullEnc) + return compressedEnc, nil } -func decodeLightClientBootstrap(enc []byte) (interfaces.LightClientBootstrap, error) { +func decodeLightClientBootstrap(enc []byte) (interfaces.LightClientBootstrap, []byte, 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") + return nil, nil, errors.Wrap(err, "could not snappy decode light client bootstrap") } var m proto.Message + var syncCommitteeHash []byte 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") + if err := bootstrap.UnmarshalSSZ(enc[len(altairKey)+32:]); err != nil { + return nil, nil, errors.Wrap(err, "could not unmarshal Altair light client bootstrap") } m = bootstrap + syncCommitteeHash = enc[len(altairKey) : len(altairKey)+32] 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") + if err := bootstrap.UnmarshalSSZ(enc[len(capellaKey)+32:]); err != nil { + return nil, nil, errors.Wrap(err, "could not unmarshal Capella light client bootstrap") } m = bootstrap + syncCommitteeHash = enc[len(capellaKey) : len(capellaKey)+32] 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") + if err := bootstrap.UnmarshalSSZ(enc[len(denebKey)+32:]); err != nil { + return nil, nil, errors.Wrap(err, "could not unmarshal Deneb light client bootstrap") } m = bootstrap + syncCommitteeHash = enc[len(denebKey) : len(denebKey)+32] 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") + if err := bootstrap.UnmarshalSSZ(enc[len(electraKey)+32:]); err != nil { + return nil, nil, errors.Wrap(err, "could not unmarshal Electra light client bootstrap") } m = bootstrap + syncCommitteeHash = enc[len(electraKey) : len(electraKey)+32] default: - return nil, errors.New("decoding of saved light client bootstrap is unsupported") + return nil, nil, errors.New("decoding of saved light client bootstrap is unsupported") } - return light_client.NewWrappedBootstrap(m) + bootstrap, err := light_client.NewWrappedBootstrap(m) + return bootstrap, syncCommitteeHash, err } func (s *Store) LightClientUpdates(ctx context.Context, startPeriod, endPeriod uint64) (map[uint64]interfaces.LightClientUpdate, error) { diff --git a/beacon-chain/db/kv/lightclient_test.go b/beacon-chain/db/kv/lightclient_test.go index 4c526933a4..ad5b879f8d 100644 --- a/beacon-chain/db/kv/lightclient_test.go +++ b/beacon-chain/db/kv/lightclient_test.go @@ -18,6 +18,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" "github.com/prysmaticlabs/prysm/v5/time/slots" + bolt "go.etcd.io/bbolt" "google.golang.org/protobuf/proto" ) @@ -611,7 +612,13 @@ func TestStore_LightClientBootstrap_CanSaveRetrieve(t *testing.T) { retrievedBootstrap, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair")) require.NoError(t, err) - require.DeepEqual(t, bootstrap, retrievedBootstrap, "retrieved bootstrap does not match saved bootstrap") + require.DeepEqual(t, bootstrap.Header(), retrievedBootstrap.Header(), "retrieved bootstrap header does not match saved bootstrap header") + require.DeepEqual(t, bootstrap.CurrentSyncCommittee(), retrievedBootstrap.CurrentSyncCommittee(), "retrieved bootstrap sync committee does not match saved bootstrap sync committee") + savedBranch, err := bootstrap.CurrentSyncCommitteeBranch() + require.NoError(t, err) + retrievedBranch, err := retrievedBootstrap.CurrentSyncCommitteeBranch() + require.NoError(t, err) + require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap sync committee branch does not match saved bootstrap sync committee branch") }) t.Run("Capella", func(t *testing.T) { @@ -626,7 +633,13 @@ func TestStore_LightClientBootstrap_CanSaveRetrieve(t *testing.T) { retrievedBootstrap, err := db.LightClientBootstrap(ctx, []byte("blockRootCapella")) require.NoError(t, err) - require.DeepEqual(t, bootstrap, retrievedBootstrap, "retrieved bootstrap does not match saved bootstrap") + require.DeepEqual(t, bootstrap.Header(), retrievedBootstrap.Header(), "retrieved bootstrap header does not match saved bootstrap header") + require.DeepEqual(t, bootstrap.CurrentSyncCommittee(), retrievedBootstrap.CurrentSyncCommittee(), "retrieved bootstrap sync committee does not match saved bootstrap sync committee") + savedBranch, err := bootstrap.CurrentSyncCommitteeBranch() + require.NoError(t, err) + retrievedBranch, err := retrievedBootstrap.CurrentSyncCommitteeBranch() + require.NoError(t, err) + require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap sync committee branch does not match saved bootstrap sync committee branch") }) t.Run("Deneb", func(t *testing.T) { @@ -641,7 +654,13 @@ func TestStore_LightClientBootstrap_CanSaveRetrieve(t *testing.T) { retrievedBootstrap, err := db.LightClientBootstrap(ctx, []byte("blockRootDeneb")) require.NoError(t, err) - require.DeepEqual(t, bootstrap, retrievedBootstrap, "retrieved bootstrap does not match saved bootstrap") + require.DeepEqual(t, bootstrap.Header(), retrievedBootstrap.Header(), "retrieved bootstrap header does not match saved bootstrap header") + require.DeepEqual(t, bootstrap.CurrentSyncCommittee(), retrievedBootstrap.CurrentSyncCommittee(), "retrieved bootstrap sync committee does not match saved bootstrap sync committee") + savedBranch, err := bootstrap.CurrentSyncCommitteeBranch() + require.NoError(t, err) + retrievedBranch, err := retrievedBootstrap.CurrentSyncCommitteeBranch() + require.NoError(t, err) + require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap sync committee branch does not match saved bootstrap sync committee branch") }) t.Run("Electra", func(t *testing.T) { @@ -656,10 +675,138 @@ func TestStore_LightClientBootstrap_CanSaveRetrieve(t *testing.T) { retrievedBootstrap, err := db.LightClientBootstrap(ctx, []byte("blockRootElectra")) require.NoError(t, err) - require.DeepEqual(t, bootstrap, retrievedBootstrap, "retrieved bootstrap does not match saved bootstrap") + require.DeepEqual(t, bootstrap.Header(), retrievedBootstrap.Header(), "retrieved bootstrap header does not match saved bootstrap header") + require.DeepEqual(t, bootstrap.CurrentSyncCommittee(), retrievedBootstrap.CurrentSyncCommittee(), "retrieved bootstrap sync committee does not match saved bootstrap sync committee") + savedBranch, err := bootstrap.CurrentSyncCommitteeBranchElectra() + require.NoError(t, err) + retrievedBranch, err := retrievedBootstrap.CurrentSyncCommitteeBranchElectra() + require.NoError(t, err) + require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap sync committee branch does not match saved bootstrap sync committee branch") }) } +func TestStore_LightClientBootstrap_MultipleBootstrapsWithSameSyncCommittee(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.BeaconConfig() + cfg.AltairForkEpoch = 0 + cfg.CapellaForkEpoch = 1 + cfg.DenebForkEpoch = 2 + cfg.ElectraForkEpoch = 3 + cfg.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(cfg) + + db := setupDB(t) + ctx := context.Background() + + bootstrap1, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch))) + require.NoError(t, err) + bootstrap2, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch))) + require.NoError(t, err) + + randomSyncCommittee := createRandomSyncCommittee() + + err = bootstrap1.SetCurrentSyncCommittee(randomSyncCommittee) + require.NoError(t, err) + err = bootstrap2.SetCurrentSyncCommittee(randomSyncCommittee) + require.NoError(t, err) + + err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair1"), bootstrap1) + require.NoError(t, err) + err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair2"), bootstrap2) + require.NoError(t, err) + + retrievedBootstrap1, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair1")) + require.NoError(t, err) + retrievedBootstrap2, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair2")) + require.NoError(t, err) + + require.DeepEqual(t, bootstrap1.Header(), retrievedBootstrap1.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header") + require.DeepEqual(t, randomSyncCommittee, retrievedBootstrap1.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee") + savedBranch, err := bootstrap1.CurrentSyncCommitteeBranch() + require.NoError(t, err) + retrievedBranch, err := retrievedBootstrap1.CurrentSyncCommitteeBranch() + require.NoError(t, err) + require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch") + + require.DeepEqual(t, bootstrap2.Header(), retrievedBootstrap2.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header") + require.DeepEqual(t, randomSyncCommittee, retrievedBootstrap2.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee") + savedBranch2, err := bootstrap2.CurrentSyncCommitteeBranch() + require.NoError(t, err) + retrievedBranch2, err := retrievedBootstrap2.CurrentSyncCommitteeBranch() + require.NoError(t, err) + require.DeepEqual(t, savedBranch2, retrievedBranch2, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch") + + // Ensure that the sync committee is only stored once + err = db.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket(lightClientSyncCommitteeBucket) + require.NotNil(t, bucket) + count := bucket.Stats().KeyN + require.Equal(t, 1, count) + return nil + }) + require.NoError(t, err) +} + +func TestStore_LightClientBootstrap_MultipleBootstrapsWithDifferentSyncCommittees(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.BeaconConfig() + cfg.AltairForkEpoch = 0 + cfg.CapellaForkEpoch = 1 + cfg.DenebForkEpoch = 2 + cfg.ElectraForkEpoch = 3 + cfg.EpochsPerSyncCommitteePeriod = 1 + params.OverrideBeaconConfig(cfg) + + db := setupDB(t) + ctx := context.Background() + + bootstrap1, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch))) + require.NoError(t, err) + bootstrap2, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch))) + require.NoError(t, err) + + err = bootstrap1.SetCurrentSyncCommittee(createRandomSyncCommittee()) + require.NoError(t, err) + err = bootstrap2.SetCurrentSyncCommittee(createRandomSyncCommittee()) + require.NoError(t, err) + + err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair1"), bootstrap1) + require.NoError(t, err) + err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair2"), bootstrap2) + require.NoError(t, err) + + retrievedBootstrap1, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair1")) + require.NoError(t, err) + retrievedBootstrap2, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair2")) + require.NoError(t, err) + + require.DeepEqual(t, bootstrap1.Header(), retrievedBootstrap1.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header") + require.DeepEqual(t, bootstrap1.CurrentSyncCommittee(), retrievedBootstrap1.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee") + savedBranch, err := bootstrap1.CurrentSyncCommitteeBranch() + require.NoError(t, err) + retrievedBranch, err := retrievedBootstrap1.CurrentSyncCommitteeBranch() + require.NoError(t, err) + require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch") + + require.DeepEqual(t, bootstrap2.Header(), retrievedBootstrap2.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header") + require.DeepEqual(t, bootstrap2.CurrentSyncCommittee(), retrievedBootstrap2.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee") + savedBranch2, err := bootstrap2.CurrentSyncCommitteeBranch() + require.NoError(t, err) + retrievedBranch2, err := retrievedBootstrap2.CurrentSyncCommitteeBranch() + require.NoError(t, err) + require.DeepEqual(t, savedBranch2, retrievedBranch2, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch") + + // Ensure that the sync committee is stored twice + err = db.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket(lightClientSyncCommitteeBucket) + require.NotNil(t, bucket) + count := bucket.Stats().KeyN + require.Equal(t, 2, count) + return nil + }) + require.NoError(t, err) +} + func createDefaultLightClientBootstrap(currentSlot primitives.Slot) (interfaces.LightClientBootstrap, error) { currentEpoch := slots.ToEpoch(currentSlot) syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize diff --git a/beacon-chain/db/kv/schema.go b/beacon-chain/db/kv/schema.go index 378f10e412..7d359a9897 100644 --- a/beacon-chain/db/kv/schema.go +++ b/beacon-chain/db/kv/schema.go @@ -18,8 +18,9 @@ var ( registrationBucket = []byte("registration") // Light Client Updates Bucket - lightClientUpdatesBucket = []byte("light-client-updates") - lightClientBootstrapBucket = []byte("light-client-bootstrap") + lightClientUpdatesBucket = []byte("light-client-updates") + lightClientBootstrapBucket = []byte("light-client-bootstrap") + lightClientSyncCommitteeBucket = []byte("light-client-sync-committee") // Deprecated: This bucket was migrated in PR 6461. Do not use, except for migrations. slotsHasObjectBucket = []byte("slots-has-objects") diff --git a/consensus-types/interfaces/light_client.go b/consensus-types/interfaces/light_client.go index 867ceb281a..1a65763055 100644 --- a/consensus-types/interfaces/light_client.go +++ b/consensus-types/interfaces/light_client.go @@ -26,6 +26,7 @@ type LightClientHeader interface { type LightClientBootstrap interface { ssz.Marshaler Version() int + Proto() proto.Message Header() LightClientHeader SetHeader(header LightClientHeader) error CurrentSyncCommittee() *pb.SyncCommittee diff --git a/consensus-types/light-client/bootstrap.go b/consensus-types/light-client/bootstrap.go index 7201b716b8..e9e583a657 100644 --- a/consensus-types/light-client/bootstrap.go +++ b/consensus-types/light-client/bootstrap.go @@ -86,6 +86,10 @@ func (h *bootstrapAltair) Version() int { return version.Altair } +func (h *bootstrapAltair) Proto() proto.Message { + return h.p +} + func (h *bootstrapAltair) Header() interfaces.LightClientHeader { return h.header } @@ -187,6 +191,10 @@ func (h *bootstrapCapella) Version() int { return version.Capella } +func (h *bootstrapCapella) Proto() proto.Message { + return h.p +} + func (h *bootstrapCapella) Header() interfaces.LightClientHeader { return h.header } @@ -288,6 +296,10 @@ func (h *bootstrapDeneb) Version() int { return version.Deneb } +func (h *bootstrapDeneb) Proto() proto.Message { + return h.p +} + func (h *bootstrapDeneb) Header() interfaces.LightClientHeader { return h.header } @@ -389,6 +401,10 @@ func (h *bootstrapElectra) Version() int { return version.Electra } +func (h *bootstrapElectra) Proto() proto.Message { + return h.p +} + func (h *bootstrapElectra) Header() interfaces.LightClientHeader { return h.header }