diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index 3f582cdd84..d0b5669b62 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "chain_info.go", "head.go", + "head_sync_committee_info.go", "info.go", "init_sync_process_block.go", "log.go", @@ -26,6 +27,7 @@ go_library( deps = [ "//beacon-chain/cache:go_default_library", "//beacon-chain/cache/depositcache:go_default_library", + "//beacon-chain/core/altair:go_default_library", "//beacon-chain/core/epoch/precompute:go_default_library", "//beacon-chain/core/feed:go_default_library", "//beacon-chain/core/feed/state:go_default_library", @@ -56,6 +58,7 @@ go_library( "//shared/slotutil:go_default_library", "//shared/timeutils:go_default_library", "//shared/traceutil:go_default_library", + "//shared/version:go_default_library", "@com_github_emicklei_dot//:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", @@ -81,9 +84,11 @@ go_test( "blockchain_test.go", "chain_info_test.go", "checktags_test.go", + "head_sync_committee_info_test.go", "head_test.go", "info_test.go", "init_test.go", + "log_test.go", "metrics_test.go", "process_attestation_test.go", "process_block_test.go", diff --git a/beacon-chain/blockchain/chain_info.go b/beacon-chain/blockchain/chain_info.go index f9f9e768ce..7f4ed372f9 100644 --- a/beacon-chain/blockchain/chain_info.go +++ b/beacon-chain/blockchain/chain_info.go @@ -24,6 +24,8 @@ type ChainInfoFetcher interface { GenesisFetcher CanonicalFetcher ForkFetcher + TimeFetcher + HeadDomainFetcher } // TimeFetcher retrieves the Ethereum consensus data that's related to time. @@ -48,8 +50,12 @@ type HeadFetcher interface { HeadSeed(ctx context.Context, epoch types.Epoch) ([32]byte, error) HeadGenesisValidatorRoot() [32]byte HeadETH1Data() *ethpb.Eth1Data + HeadPublicKeyToValidatorIndex(ctx context.Context, pubKey [48]byte) (types.ValidatorIndex, bool) + HeadValidatorIndexToPublicKey(ctx context.Context, index types.ValidatorIndex) ([48]byte, error) ProtoArrayStore() *protoarray.Store ChainHeads() ([][32]byte, []types.Slot) + HeadSyncCommitteeFetcher + HeadDomainFetcher } // ForkFetcher retrieves the current fork information of the Ethereum beacon chain. diff --git a/beacon-chain/blockchain/head_sync_committee_info.go b/beacon-chain/blockchain/head_sync_committee_info.go new file mode 100644 index 0000000000..4958253608 --- /dev/null +++ b/beacon-chain/blockchain/head_sync_committee_info.go @@ -0,0 +1,142 @@ +package blockchain + +import ( + "context" + + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/cache" + "github.com/prysmaticlabs/prysm/beacon-chain/core/altair" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + core "github.com/prysmaticlabs/prysm/beacon-chain/core/state" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/params" +) + +// Initialize the state cache for sync committees. +var syncCommitteeHeadStateCache = cache.NewSyncCommitteeHeadState() + +// HeadSyncCommitteeFetcher is the interface that wraps the head sync committee related functions. +// The head sync committee functions return callers sync committee indices and public keys with respect to current head state. +type HeadSyncCommitteeFetcher interface { + HeadCurrentSyncCommitteeIndices(ctx context.Context, index types.ValidatorIndex, slot types.Slot) ([]types.CommitteeIndex, error) + HeadNextSyncCommitteeIndices(ctx context.Context, index types.ValidatorIndex, slot types.Slot) ([]types.CommitteeIndex, error) + HeadSyncCommitteePubKeys(ctx context.Context, slot types.Slot, committeeIndex types.CommitteeIndex) ([][]byte, error) +} + +// HeadDomainFetcher is the interface that wraps the head sync domain related functions. +// The head sync committee domain functions return callers domain data with respect to slot and head state. +type HeadDomainFetcher interface { + HeadSyncCommitteeDomain(ctx context.Context, slot types.Slot) ([]byte, error) + HeadSyncSelectionProofDomain(ctx context.Context, slot types.Slot) ([]byte, error) + HeadSyncContributionProofDomain(ctx context.Context, slot types.Slot) ([]byte, error) +} + +// HeadSyncCommitteeDomain returns the head sync committee domain using current head state advanced up to `slot`. +func (s *Service) HeadSyncCommitteeDomain(ctx context.Context, slot types.Slot) ([]byte, error) { + return s.domainWithHeadState(ctx, slot, params.BeaconConfig().DomainSyncCommittee) +} + +// HeadSyncSelectionProofDomain returns the head sync committee domain using current head state advanced up to `slot`. +func (s *Service) HeadSyncSelectionProofDomain(ctx context.Context, slot types.Slot) ([]byte, error) { + return s.domainWithHeadState(ctx, slot, params.BeaconConfig().DomainSyncCommitteeSelectionProof) +} + +// HeadSyncContributionProofDomain returns the head sync committee domain using current head state advanced up to `slot`. +func (s *Service) HeadSyncContributionProofDomain(ctx context.Context, slot types.Slot) ([]byte, error) { + return s.domainWithHeadState(ctx, slot, params.BeaconConfig().DomainContributionAndProof) +} + +// HeadCurrentSyncCommitteeIndices returns the input validator `index`'s position indices in the current sync committee with respect to `slot`. +// Head state advanced up to `slot` is used for calculation. +func (s *Service) HeadCurrentSyncCommitteeIndices(ctx context.Context, index types.ValidatorIndex, slot types.Slot) ([]types.CommitteeIndex, error) { + headState, err := s.getSyncCommitteeHeadState(ctx, slot) + if err != nil { + return nil, err + } + return helpers.CurrentPeriodSyncSubcommitteeIndices(headState, index) +} + +// HeadNextSyncCommitteeIndices returns the input validator `index`'s position indices in the next sync committee with respect to `slot`. +// Head state advanced up to `slot` is used for calculation. +func (s *Service) HeadNextSyncCommitteeIndices(ctx context.Context, index types.ValidatorIndex, slot types.Slot) ([]types.CommitteeIndex, error) { + headState, err := s.getSyncCommitteeHeadState(ctx, slot) + if err != nil { + return nil, err + } + return helpers.NextPeriodSyncSubcommitteeIndices(headState, index) +} + +// HeadSyncCommitteePubKeys returns the head sync committee public keys with respect to `slot` and subcommittee index `committeeIndex`. +// Head state advanced up to `slot` is used for calculation. +func (s *Service) HeadSyncCommitteePubKeys(ctx context.Context, slot types.Slot, committeeIndex types.CommitteeIndex) ([][]byte, error) { + headState, err := s.getSyncCommitteeHeadState(ctx, slot) + if err != nil { + return nil, err + } + + nextSlotEpoch := helpers.SlotToEpoch(headState.Slot() + 1) + currEpoch := helpers.SlotToEpoch(headState.Slot()) + + var syncCommittee *ethpb.SyncCommittee + if currEpoch == nextSlotEpoch || helpers.SyncCommitteePeriod(currEpoch) == helpers.SyncCommitteePeriod(nextSlotEpoch) { + syncCommittee, err = headState.CurrentSyncCommittee() + if err != nil { + return nil, err + } + } else { + syncCommittee, err = headState.NextSyncCommittee() + if err != nil { + return nil, err + } + } + + return altair.SyncSubCommitteePubkeys(syncCommittee, committeeIndex) +} + +// returns calculated domain using input `domain` and `slot`. +func (s *Service) domainWithHeadState(ctx context.Context, slot types.Slot, domain [4]byte) ([]byte, error) { + headState, err := s.getSyncCommitteeHeadState(ctx, slot) + if err != nil { + return nil, err + } + return helpers.Domain(headState.Fork(), helpers.SlotToEpoch(headState.Slot()), domain, headState.GenesisValidatorRoot()) +} + +// returns the head state that is advanced up to `slot`. It utilizes the cache `syncCommitteeHeadState` by retrieving using `slot` as key. +// For the cache miss, it processes head state up to slot and fill the cache with `slot` as key. +func (s *Service) getSyncCommitteeHeadState(ctx context.Context, slot types.Slot) (state.BeaconState, error) { + var headState state.BeaconState + var err error + + // If there's already a head state exists with the request slot, we don't need to process slots. + cachedState, err := syncCommitteeHeadStateCache.Get(slot) + switch { + case err == nil: + syncHeadStateHit.Inc() + headState = cachedState + return headState, nil + case errors.Is(err, cache.ErrNotFound): + headState, err = s.HeadState(ctx) + if err != nil { + return nil, err + } + if headState == nil || headState.IsNil() { + return nil, errors.New("nil state") + } + if slot > headState.Slot() { + headState, err = core.ProcessSlots(ctx, headState, slot) + if err != nil { + return nil, err + } + } + syncHeadStateMiss.Inc() + err = syncCommitteeHeadStateCache.Put(slot, headState) + return headState, err + default: + // In the event, we encounter another error + // we return it. + return nil, err + } +} diff --git a/beacon-chain/blockchain/head_sync_committee_info_test.go b/beacon-chain/blockchain/head_sync_committee_info_test.go new file mode 100644 index 0000000000..db8e1f401f --- /dev/null +++ b/beacon-chain/blockchain/head_sync_committee_info_test.go @@ -0,0 +1,152 @@ +package blockchain + +import ( + "context" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/cache" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen" + "github.com/prysmaticlabs/prysm/shared/params" + "github.com/prysmaticlabs/prysm/shared/testutil" + "github.com/prysmaticlabs/prysm/shared/testutil/require" +) + +func TestService_HeadSyncCommitteeFetcher_Errors(t *testing.T) { + beaconDB := dbtest.SetupDB(t) + c := &Service{ + cfg: &Config{ + StateGen: stategen.New(beaconDB), + }, + } + c.head = &head{} + _, err := c.HeadCurrentSyncCommitteeIndices(context.Background(), types.ValidatorIndex(0), types.Slot(0)) + require.ErrorContains(t, "nil state", err) + + _, err = c.HeadNextSyncCommitteeIndices(context.Background(), types.ValidatorIndex(0), types.Slot(0)) + require.ErrorContains(t, "nil state", err) + + _, err = c.HeadSyncCommitteePubKeys(context.Background(), types.Slot(0), types.CommitteeIndex(0)) + require.ErrorContains(t, "nil state", err) +} + +func TestService_HeadDomainFetcher_Errors(t *testing.T) { + beaconDB := dbtest.SetupDB(t) + c := &Service{ + cfg: &Config{ + StateGen: stategen.New(beaconDB), + }, + } + c.head = &head{} + _, err := c.HeadSyncCommitteeDomain(context.Background(), types.Slot(0)) + require.ErrorContains(t, "nil state", err) + + _, err = c.HeadSyncSelectionProofDomain(context.Background(), types.Slot(0)) + require.ErrorContains(t, "nil state", err) + + _, err = c.HeadSyncSelectionProofDomain(context.Background(), types.Slot(0)) + require.ErrorContains(t, "nil state", err) +} + +func TestService_HeadCurrentSyncCommitteeIndices(t *testing.T) { + s, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize) + c := &Service{} + c.head = &head{state: s} + + // Process slot up to `EpochsPerSyncCommitteePeriod` so it can `ProcessSyncCommitteeUpdates`. + slot := uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)*uint64(params.BeaconConfig().SlotsPerEpoch) + 1 + indices, err := c.HeadCurrentSyncCommitteeIndices(context.Background(), 0, types.Slot(slot)) + require.NoError(t, err) + + // NextSyncCommittee becomes CurrentSyncCommittee so it should be empty by default. + require.Equal(t, 0, len(indices)) +} + +func TestService_HeadNextSyncCommitteeIndices(t *testing.T) { + s, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize) + c := &Service{} + c.head = &head{state: s} + + // Process slot up to `EpochsPerSyncCommitteePeriod` so it can `ProcessSyncCommitteeUpdates`. + slot := uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)*uint64(params.BeaconConfig().SlotsPerEpoch) + 1 + indices, err := c.HeadNextSyncCommitteeIndices(context.Background(), 0, types.Slot(slot)) + require.NoError(t, err) + + // NextSyncCommittee should be be empty after `ProcessSyncCommitteeUpdates`. Validator should get indices. + require.NotEqual(t, 0, len(indices)) +} + +func TestService_HeadSyncCommitteePubKeys(t *testing.T) { + s, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize) + c := &Service{} + c.head = &head{state: s} + + // Process slot up to 2 * `EpochsPerSyncCommitteePeriod` so it can run `ProcessSyncCommitteeUpdates` twice. + slot := uint64(2*params.BeaconConfig().EpochsPerSyncCommitteePeriod)*uint64(params.BeaconConfig().SlotsPerEpoch) + 1 + pubkeys, err := c.HeadSyncCommitteePubKeys(context.Background(), types.Slot(slot), 0) + require.NoError(t, err) + + // Any subcommittee should match the subcommittee size. + subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount + require.Equal(t, int(subCommitteeSize), len(pubkeys)) +} + +func TestService_HeadSyncCommitteeDomain(t *testing.T) { + s, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize) + c := &Service{} + c.head = &head{state: s} + + wanted, err := helpers.Domain(s.Fork(), helpers.SlotToEpoch(s.Slot()), params.BeaconConfig().DomainSyncCommittee, s.GenesisValidatorRoot()) + require.NoError(t, err) + + d, err := c.HeadSyncCommitteeDomain(context.Background(), 0) + require.NoError(t, err) + + require.DeepEqual(t, wanted, d) +} + +func TestService_HeadSyncContributionProofDomain(t *testing.T) { + s, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize) + c := &Service{} + c.head = &head{state: s} + + wanted, err := helpers.Domain(s.Fork(), helpers.SlotToEpoch(s.Slot()), params.BeaconConfig().DomainContributionAndProof, s.GenesisValidatorRoot()) + require.NoError(t, err) + + d, err := c.HeadSyncContributionProofDomain(context.Background(), 0) + require.NoError(t, err) + + require.DeepEqual(t, wanted, d) +} + +func TestService_HeadSyncSelectionProofDomain(t *testing.T) { + s, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize) + c := &Service{} + c.head = &head{state: s} + + wanted, err := helpers.Domain(s.Fork(), helpers.SlotToEpoch(s.Slot()), params.BeaconConfig().DomainSyncCommitteeSelectionProof, s.GenesisValidatorRoot()) + require.NoError(t, err) + + d, err := c.HeadSyncSelectionProofDomain(context.Background(), 0) + require.NoError(t, err) + + require.DeepEqual(t, wanted, d) +} + +func TestSyncCommitteeHeadStateCache_RoundTrip(t *testing.T) { + c := syncCommitteeHeadStateCache + t.Cleanup(func() { + syncCommitteeHeadStateCache = cache.NewSyncCommitteeHeadState() + }) + beaconState, _ := testutil.DeterministicGenesisStateAltair(t, 100) + require.NoError(t, beaconState.SetSlot(100)) + cachedState, err := c.Get(101) + require.ErrorContains(t, cache.ErrNotFound.Error(), err) + require.Equal(t, nil, cachedState) + require.NoError(t, c.Put(101, beaconState)) + cachedState, err = c.Get(101) + require.NoError(t, err) + require.DeepEqual(t, beaconState, cachedState) +} diff --git a/beacon-chain/blockchain/log.go b/beacon-chain/blockchain/log.go index e98d56aa22..92b72b8884 100644 --- a/beacon-chain/blockchain/log.go +++ b/beacon-chain/blockchain/log.go @@ -10,6 +10,7 @@ import ( "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/timeutils" + "github.com/prysmaticlabs/prysm/shared/version" "github.com/sirupsen/logrus" ) @@ -33,6 +34,12 @@ func logStateTransitionData(b block.BeaconBlock) { if len(b.Body().VoluntaryExits()) > 0 { log = log.WithField("voluntaryExits", len(b.Body().VoluntaryExits())) } + if b.Version() == version.Altair { + agg, err := b.Body().SyncAggregate() + if err == nil { + log = log.WithField("syncBitsCount", agg.SyncCommitteeBits.Count()) + } + } log.Info("Finished applying state transition") } diff --git a/beacon-chain/blockchain/metrics.go b/beacon-chain/blockchain/metrics.go index d72881292d..a240e9a0c7 100644 --- a/beacon-chain/blockchain/metrics.go +++ b/beacon-chain/blockchain/metrics.go @@ -3,15 +3,18 @@ package blockchain import ( "context" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/core/altair" "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute" "github.com/prysmaticlabs/prysm/beacon-chain/state" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" + "github.com/prysmaticlabs/prysm/shared/version" ) var ( @@ -111,6 +114,14 @@ var ( Buckets: []float64{1, 2, 3, 4, 6, 32, 64}, }, ) + syncHeadStateMiss = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sync_head_state_miss", + Help: "The number of sync head state requests that are not present in the cache.", + }) + syncHeadStateHit = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sync_head_state_hit", + Help: "The number of sync head state requests that are present in the cache.", + }) ) // reportSlotMetrics reports slot related metrics. @@ -210,14 +221,31 @@ func reportEpochMetrics(ctx context.Context, postState, headState state.BeaconSt beaconFinalizedRoot.Set(float64(bytesutil.ToLowInt64(postState.FinalizedCheckpoint().Root))) currentEth1DataDepositCount.Set(float64(postState.Eth1Data().DepositCount)) - // Validator participation should be viewed on the canonical chain. - v, b, err := precompute.New(ctx, headState) - if err != nil { - return err - } - _, b, err = precompute.ProcessAttestations(ctx, headState, v, b) - if err != nil { - return err + var b *precompute.Balance + var v []*precompute.Validator + var err error + switch headState.Version() { + case version.Phase0: + // Validator participation should be viewed on the canonical chain. + v, b, err = precompute.New(ctx, headState) + if err != nil { + return err + } + _, b, err = precompute.ProcessAttestations(ctx, headState, v, b) + if err != nil { + return err + } + case version.Altair: + v, b, err = altair.InitializeEpochValidators(ctx, headState) + if err != nil { + return err + } + _, b, err = altair.ProcessEpochParticipation(ctx, headState, b, v) + if err != nil { + return err + } + default: + return errors.Errorf("invalid state type provided: %T", headState.InnerStateUnsafe()) } prevEpochActiveBalances.Set(float64(b.ActivePrevEpoch)) prevEpochSourceBalances.Set(float64(b.PrevEpochAttested)) diff --git a/beacon-chain/blockchain/testing/mock.go b/beacon-chain/blockchain/testing/mock.go index 57e3b88d10..d2cd1c8bff 100644 --- a/beacon-chain/blockchain/testing/mock.go +++ b/beacon-chain/blockchain/testing/mock.go @@ -21,7 +21,6 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/state" v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" - statepb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/event" @@ -42,7 +41,7 @@ type ChainService struct { Genesis time.Time ValidatorsRoot [32]byte CanonicalRoots map[[32]byte]bool - Fork *statepb.Fork + Fork *ethpb.Fork ETH1Data *ethpb.Eth1Data DB db.Database stateNotifier statefeed.Notifier @@ -52,6 +51,13 @@ type ChainService struct { ForkChoiceStore *protoarray.Store VerifyBlkDescendantErr error Slot *types.Slot // Pointer because 0 is a useful value, so checking against it can be incorrect. + CurrentSyncCommitteeIndices []types.CommitteeIndex + NextSyncCommitteeIndices []types.CommitteeIndex + SyncCommitteeDomain []byte + SyncSelectionProofDomain []byte + SyncContributionProofDomain []byte + PublicKey [48]byte + SyncCommitteePubkeys [][]byte } // StateNotifier mocks the same method in the chain service. @@ -259,7 +265,7 @@ func (s *ChainService) HeadState(context.Context) (state.BeaconState, error) { } // CurrentFork mocks HeadState method in chain service. -func (s *ChainService) CurrentFork() *statepb.Fork { +func (s *ChainService) CurrentFork() *ethpb.Fork { return s.Fork } @@ -393,3 +399,43 @@ func (s *ChainService) ChainHeads() ([][32]byte, []types.Slot) { }, []types.Slot{0, 1} } + +// HeadPublicKeyToValidatorIndex mocks HeadPublicKeyToValidatorIndex and always return 0 and true. +func (s *ChainService) HeadPublicKeyToValidatorIndex(ctx context.Context, pubKey [48]byte) (types.ValidatorIndex, bool) { + return 0, true +} + +// HeadValidatorIndexToPublicKey mocks HeadValidatorIndexToPublicKey and always return empty and nil. +func (s *ChainService) HeadValidatorIndexToPublicKey(ctx context.Context, index types.ValidatorIndex) ([48]byte, error) { + return s.PublicKey, nil +} + +// HeadCurrentSyncCommitteeIndices mocks HeadCurrentSyncCommitteeIndices and always return `CurrentSyncCommitteeIndices`. +func (s *ChainService) HeadCurrentSyncCommitteeIndices(ctx context.Context, index types.ValidatorIndex, slot types.Slot) ([]types.CommitteeIndex, error) { + return s.CurrentSyncCommitteeIndices, nil +} + +// HeadNextSyncCommitteeIndices mocks HeadNextSyncCommitteeIndices and always return `HeadNextSyncCommitteeIndices`. +func (s *ChainService) HeadNextSyncCommitteeIndices(ctx context.Context, index types.ValidatorIndex, slot types.Slot) ([]types.CommitteeIndex, error) { + return s.NextSyncCommitteeIndices, nil +} + +// HeadSyncCommitteePubKeys mocks HeadSyncCommitteePubKeys and always return empty nil. +func (s *ChainService) HeadSyncCommitteePubKeys(ctx context.Context, slot types.Slot, committeeIndex types.CommitteeIndex) ([][]byte, error) { + return s.SyncCommitteePubkeys, nil +} + +// HeadSyncCommitteeDomain mocks HeadSyncCommitteeDomain and always return empty nil. +func (s *ChainService) HeadSyncCommitteeDomain(ctx context.Context, slot types.Slot) ([]byte, error) { + return s.SyncCommitteeDomain, nil +} + +// HeadSyncSelectionProofDomain mocks HeadSyncSelectionProofDomain and always return empty nil. +func (s *ChainService) HeadSyncSelectionProofDomain(ctx context.Context, slot types.Slot) ([]byte, error) { + return s.SyncSelectionProofDomain, nil +} + +// HeadSyncContributionProofDomain mocks HeadSyncContributionProofDomain and always return empty nil. +func (s *ChainService) HeadSyncContributionProofDomain(ctx context.Context, slot types.Slot) ([]byte, error) { + return s.SyncContributionProofDomain, nil +} diff --git a/beacon-chain/cache/error.go b/beacon-chain/cache/error.go index b99153fb7b..34f9d89e25 100644 --- a/beacon-chain/cache/error.go +++ b/beacon-chain/cache/error.go @@ -3,6 +3,10 @@ package cache import "errors" var ( + // ErrNilValueProvided for when we try to put a nil value in a cache. + ErrNilValueProvided = errors.New("nil value provided on Put()") + // ErrIncorrectType for when the state is of the incorrect type. + ErrIncorrectType = errors.New("incorrect state type provided") // ErrNotFound for cache fetches that return a nil value. ErrNotFound = errors.New("not found in cache") // ErrNonExistingSyncCommitteeKey when sync committee key (root) does not exist in cache. diff --git a/beacon-chain/cache/sync_committee_head_state.go b/beacon-chain/cache/sync_committee_head_state.go index 347279698c..925b1a9a00 100644 --- a/beacon-chain/cache/sync_committee_head_state.go +++ b/beacon-chain/cache/sync_committee_head_state.go @@ -16,19 +16,29 @@ type SyncCommitteeHeadStateCache struct { } // NewSyncCommitteeHeadState initializes a LRU cache for `SyncCommitteeHeadState` with size of 1. -func NewSyncCommitteeHeadState() (*SyncCommitteeHeadStateCache, error) { +func NewSyncCommitteeHeadState() *SyncCommitteeHeadStateCache { c, err := lru.New(1) // only need size of 1 to avoid redundant state copies, hashing, and slot processing. if err != nil { - return nil, err + panic(err) } - return &SyncCommitteeHeadStateCache{cache: c}, nil + return &SyncCommitteeHeadStateCache{cache: c} } // Put `slot` as key and `state` as value onto the cache. -func (c *SyncCommitteeHeadStateCache) Put(slot types.Slot, st state.BeaconState) { +func (c *SyncCommitteeHeadStateCache) Put(slot types.Slot, st state.BeaconState) error { c.lock.Lock() defer c.lock.Unlock() + // Make sure that the provided state is non nil + // and is of the correct type. + if st == nil || st.IsNil() { + return ErrNilValueProvided + } + _, ok := st.(*stateAltair.BeaconState) + if !ok { + return ErrIncorrectType + } c.cache.Add(slot, st) + return nil } // Get `state` using `slot` as key. Return nil if nothing is found. @@ -36,8 +46,12 @@ func (c *SyncCommitteeHeadStateCache) Get(slot types.Slot) (state.BeaconState, e c.lock.RLock() defer c.lock.RUnlock() val, exists := c.cache.Get(slot) - if !exists || val == nil { + if !exists { return nil, ErrNotFound } - return val.(*stateAltair.BeaconState), nil + st, ok := val.(*stateAltair.BeaconState) + if !ok { + return nil, ErrIncorrectType + } + return st, nil } diff --git a/beacon-chain/cache/sync_committee_head_state_test.go b/beacon-chain/cache/sync_committee_head_state_test.go index c590917557..c4b91f78f8 100644 --- a/beacon-chain/cache/sync_committee_head_state_test.go +++ b/beacon-chain/cache/sync_committee_head_state_test.go @@ -5,22 +5,60 @@ import ( types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/beacon-chain/state" + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1" v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/v2" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil/require" ) func TestSyncCommitteeHeadState(t *testing.T) { + beaconState, err := v2.InitializeFromProto(ðpb.BeaconStateAltair{ + Fork: ðpb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + }, + }) + require.NoError(t, err) + phase0State, err := v1.InitializeFromProto(ðpb.BeaconState{ + Fork: ðpb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + }, + }) + require.NoError(t, err) type put struct { slot types.Slot state state.BeaconState } tests := []struct { - name string - key types.Slot - put *put - want state.BeaconState - wantErr bool + name string + key types.Slot + put *put + want state.BeaconState + wantErr bool + wantPutErr bool }{ + { + name: "putting error in", + key: types.Slot(1), + put: &put{ + slot: types.Slot(1), + state: nil, + }, + wantPutErr: true, + wantErr: true, + }, + { + name: "putting invalid state in", + key: types.Slot(1), + put: &put{ + slot: types.Slot(1), + state: phase0State, + }, + wantPutErr: true, + wantErr: true, + }, { name: "not found when empty cache", key: types.Slot(1), @@ -31,7 +69,7 @@ func TestSyncCommitteeHeadState(t *testing.T) { key: types.Slot(2), put: &put{ slot: types.Slot(1), - state: &v2.BeaconState{}, + state: beaconState, }, wantErr: true, }, @@ -40,17 +78,19 @@ func TestSyncCommitteeHeadState(t *testing.T) { key: types.Slot(1), put: &put{ slot: types.Slot(1), - state: &v2.BeaconState{}, + state: beaconState, }, - want: &v2.BeaconState{}, + want: beaconState, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c, err := NewSyncCommitteeHeadState() - require.NoError(t, err) + c := NewSyncCommitteeHeadState() if tt.put != nil { - c.Put(tt.put.slot, tt.put.state) + err := c.Put(tt.put.slot, tt.put.state) + if (err != nil) != tt.wantPutErr { + t.Fatalf("Put() error = %v, wantErr %v", err, tt.wantErr) + } } got, err := c.Get(tt.key) if (err != nil) != tt.wantErr {