From cd07ef3d853e258577afd7327646cf5a1adba77a Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Thu, 9 Mar 2023 23:07:32 +0100 Subject: [PATCH 1/7] ops --- cl/utils/bytes.go | 21 +++++ .../consensus_tests/consensus_tester.go | 6 +- .../consensus_tests/epoch_processing.go | 5 ++ cmd/ef-tests-cl/consensus_tests/handlers.go | 2 + cmd/erigon-cl/core/state/accessors.go | 79 +++++++++++++------ cmd/erigon-cl/core/state/getters.go | 4 + cmd/erigon-cl/core/state/setters.go | 39 +++++++++ cmd/erigon-cl/core/state/ssz.go | 4 +- .../finalization_and_justification.go | 52 +++++++----- .../core/transition/process_attestations.go | 79 ++++++++++++++++--- .../core/transition/process_epoch.go | 9 +-- 11 files changed, 237 insertions(+), 63 deletions(-) diff --git a/cl/utils/bytes.go b/cl/utils/bytes.go index 5909d0d9e5c..66d9aa65a1c 100644 --- a/cl/utils/bytes.go +++ b/cl/utils/bytes.go @@ -15,6 +15,7 @@ package utils import ( "encoding/binary" + "math/bits" "github.com/golang/snappy" "github.com/ledgerwatch/erigon/cl/cltypes/ssz_utils" @@ -105,3 +106,23 @@ func IsSliceSortedSet(vals []uint64) bool { } return true } + +// getBitlistLength return the amount of bits in given bitlist. +func GetBitlistLength(b []byte) int { + if len(b) == 0 { + return 0 + } + // The most significant bit is present in the last byte in the array. + last := b[len(b)-1] + + // Determine the position of the most significant bit. + msb := bits.Len8(last) + if msb == 0 { + return 0 + } + + // The absolute position of the most significant bit will be the number of + // bits in the preceding bytes plus the position of the most significant + // bit. Subtract this value by 1 to determine the length of the bitlist. + return 8*(len(b)-1) + msb - 1 +} diff --git a/cmd/ef-tests-cl/consensus_tests/consensus_tester.go b/cmd/ef-tests-cl/consensus_tests/consensus_tester.go index f38d2201318..31eb2327be9 100644 --- a/cmd/ef-tests-cl/consensus_tests/consensus_tester.go +++ b/cmd/ef-tests-cl/consensus_tests/consensus_tester.go @@ -11,7 +11,7 @@ import ( "golang.org/x/exp/slices" ) -var supportedVersions = []string{"altair", "bellatrix", "capella"} +var supportedVersions = []string{"phase0", "altair", "bellatrix", "capella"} type ConsensusTester struct { // parameters @@ -70,7 +70,7 @@ func (c *ConsensusTester) iterateOverTests(dir, p string, depth int) { // Depth 1 means that we are setting the version if depth == 1 { if !slices.Contains(supportedVersions, childName) { - return + continue } c.context.version = stringToClVersion(childName) } @@ -81,12 +81,14 @@ func (c *ConsensusTester) iterateOverTests(dir, p string, depth int) { // depth 3 we find the specific c.context.caseName = childName } + // If we found a non-directory then it is a test folder. if !childDir.IsDir() { // Check if it matches case specified. if *c.pattern != "" && !strings.Contains(p, *c.pattern) { return } + // If yes execute it. if implemented, err := c.executeTest(p); err != nil { log.Warn("Test Failed", "err", err, "test", p) diff --git a/cmd/ef-tests-cl/consensus_tests/epoch_processing.go b/cmd/ef-tests-cl/consensus_tests/epoch_processing.go index f34da2b9521..f1f7993bf87 100644 --- a/cmd/ef-tests-cl/consensus_tests/epoch_processing.go +++ b/cmd/ef-tests-cl/consensus_tests/epoch_processing.go @@ -107,3 +107,8 @@ var slashingsResetTest = getTestEpochProcessing(func(s *state.BeaconState) error transition.ProcessSlashingsReset(s) return nil }) + +var recordsResetTest = getTestEpochProcessing(func(s *state.BeaconState) error { + transition.ProcessParticipationRecordUpdates(s) + return nil +}) diff --git a/cmd/ef-tests-cl/consensus_tests/handlers.go b/cmd/ef-tests-cl/consensus_tests/handlers.go index 4dcfb34d73e..ee64315f902 100644 --- a/cmd/ef-tests-cl/consensus_tests/handlers.go +++ b/cmd/ef-tests-cl/consensus_tests/handlers.go @@ -25,6 +25,7 @@ var ( caseRewardsAndPenalties = "rewards_and_penalties" caseSlashings = "slashings" caseSlashingsReset = "slashings_reset" + caseParticipationRecords = "participation_record_updates" ) // Operations cases @@ -69,6 +70,7 @@ var handlers map[string]testFunc = map[string]testFunc{ path.Join(epochProcessingDivision, caseRewardsAndPenalties): rewardsAndPenaltiesTest, path.Join(epochProcessingDivision, caseSlashings): slashingsTest, path.Join(epochProcessingDivision, caseSlashingsReset): slashingsResetTest, + path.Join(epochProcessingDivision, caseParticipationRecords): recordsResetTest, path.Join(operationsDivision, caseAttestation): operationAttestationHandler, path.Join(operationsDivision, caseAttesterSlashing): operationAttesterSlashingHandler, path.Join(operationsDivision, caseProposerSlashing): operationProposerSlashingHandler, diff --git a/cmd/erigon-cl/core/state/accessors.go b/cmd/erigon-cl/core/state/accessors.go index 97355e7cb43..92f24b9ab99 100644 --- a/cmd/erigon-cl/core/state/accessors.go +++ b/cmd/erigon-cl/core/state/accessors.go @@ -3,7 +3,6 @@ package state import ( "encoding/binary" "fmt" - "math/bits" "sort" libcommon "github.com/ledgerwatch/erigon-lib/common" @@ -385,32 +384,12 @@ func (b *BeaconState) GetIndexedAttestation(attestation *cltypes.Attestation, at }, nil } -// getBitlistLength return the amount of bits in given bitlist. -func getBitlistLength(b []byte) int { - if len(b) == 0 { - return 0 - } - // The most significant bit is present in the last byte in the array. - last := b[len(b)-1] - - // Determine the position of the most significant bit. - msb := bits.Len8(last) - if msb == 0 { - return 0 - } - - // The absolute position of the most significant bit will be the number of - // bits in the preceding bytes plus the position of the most significant - // bit. Subtract this value by 1 to determine the length of the bitlist. - return 8*(len(b)-1) + msb - 1 -} - func (b *BeaconState) GetAttestingIndicies(attestation *cltypes.AttestationData, aggregationBits []byte) ([]uint64, error) { committee, err := b.GetBeaconCommitee(attestation.Slot, attestation.Index) if err != nil { return nil, err } - if getBitlistLength(aggregationBits) != len(committee) { + if utils.GetBitlistLength(aggregationBits) != len(committee) { return nil, fmt.Errorf("GetAttestingIndicies: invalid aggregation bits") } attestingIndices := []uint64{} @@ -542,3 +521,59 @@ func (b *BeaconState) ExpectedWithdrawals() []*types.Withdrawal { // Return the withdrawals slice return withdrawals } + +// MatchingSourceAttestations retrieves pending attestations for specific epoch. +func (b *BeaconState) MatchingSourceAttestations(epoch uint64) ([]*cltypes.PendingAttestation, error) { + if b.version == clparams.Phase0Version { + panic("can call GetMatchingSourceAttestations only on Phase0") + } + if epoch == b.PreviousEpoch() { + return b.previousEpochAttestations, nil + } else if epoch == b.Epoch() { + return b.currentEpochAttestations, nil + } else { + return nil, fmt.Errorf("GetMatchingSourceAttestations: invalid epoch") + } +} + +// MatchingTargetAttestations retrieves pending attestations for specific epoch, whose vote went on the block root. +func (b *BeaconState) MatchingTargetAttestations(epoch uint64) ([]*cltypes.PendingAttestation, error) { + if b.version == clparams.Phase0Version { + panic("can call GetMatchingTargetAttestations only on Phase0") + } + sourceAttestations, err := b.MatchingSourceAttestations(epoch) + if err != nil { + return nil, err + } + ret := []*cltypes.PendingAttestation{} + blockRoot, err := b.GetBlockRoot(epoch) + if err != nil { + return nil, err + } + // Now we filter then + for _, attestation := range sourceAttestations { + if blockRoot != attestation.Data.Target.Root { + continue + } + ret = append(ret, attestation) + } + return ret, nil +} + +// UnslashedAttestingBalance total balance of attesting indicies. +func (b *BeaconState) UnslashedAttestingBalance(attestations []*cltypes.PendingAttestation) (uint64, error) { + sum := uint64(0) + for _, attestation := range attestations { + attestingIndicies, err := b.GetAttestingIndicies(attestation.Data, attestation.AggregationBits) + if err != nil { + return 0, err + } + for _, index := range attestingIndicies { + if b.validators[index].Slashed { + continue + } + sum += b.validators[index].EffectiveBalance + } + } + return sum, nil +} diff --git a/cmd/erigon-cl/core/state/getters.go b/cmd/erigon-cl/core/state/getters.go index 2c3b024d7c4..d23225c7803 100644 --- a/cmd/erigon-cl/core/state/getters.go +++ b/cmd/erigon-cl/core/state/getters.go @@ -152,6 +152,10 @@ func (b *BeaconState) NextWithdrawalIndex() uint64 { return b.nextWithdrawalIndex } +func (b *BeaconState) CurrentEpochAttestations() []*cltypes.PendingAttestation { + return b.currentEpochAttestations +} + func (b *BeaconState) NextWithdrawalValidatorIndex() uint64 { return b.nextWithdrawalValidatorIndex } diff --git a/cmd/erigon-cl/core/state/setters.go b/cmd/erigon-cl/core/state/setters.go index 8d257c4cfa0..c7d809a1fa1 100644 --- a/cmd/erigon-cl/core/state/setters.go +++ b/cmd/erigon-cl/core/state/setters.go @@ -3,6 +3,7 @@ package state import ( libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes" ) @@ -210,11 +211,49 @@ func (b *BeaconState) SetValidatorInactivityScore(index int, score uint64) error } func (b *BeaconState) AddCurrentEpochParticipationFlags(flags cltypes.ParticipationFlags) { + if b.version == clparams.Phase0Version { + panic("cannot call AddCurrentEpochParticipationFlags on phase0") + } b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true b.currentEpochParticipation = append(b.currentEpochParticipation, flags) } func (b *BeaconState) AddPreviousEpochParticipationFlags(flags cltypes.ParticipationFlags) { + if b.version == clparams.Phase0Version { + panic("cannot call AddPreviousEpochParticipationFlags on phase0") + } b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true b.previousEpochParticipation = append(b.previousEpochParticipation, flags) } + +func (b *BeaconState) AddCurrentEpochAtteastation(attestation *cltypes.PendingAttestation) { + if b.version != clparams.Phase0Version { + panic("can call AddCurrentEpochAtteastation only on phase0") + } + b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true + b.currentEpochAttestations = append(b.currentEpochAttestations, attestation) +} + +func (b *BeaconState) AddPreviousEpochAtteastation(attestation *cltypes.PendingAttestation) { + if b.version != clparams.Phase0Version { + panic("can call AddPreviousEpochAtteastation only on phase0") + } + b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true + b.previousEpochAttestations = append(b.previousEpochAttestations, attestation) +} + +func (b *BeaconState) SetCurrentEpochAtteastations(attestations []*cltypes.PendingAttestation) { + if b.version != clparams.Phase0Version { + panic("can call SetCurrentEpochAtteastations only on phase0") + } + b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true + b.currentEpochAttestations = attestations +} + +func (b *BeaconState) SetPreviousEpochAtteastations(attestations []*cltypes.PendingAttestation) { + if b.version != clparams.Phase0Version { + panic("can call SetPreviousEpochAtteastations only on phase0") + } + b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true + b.previousEpochAttestations = attestations +} diff --git a/cmd/erigon-cl/core/state/ssz.go b/cmd/erigon-cl/core/state/ssz.go index ab9036688ff..3891331fb57 100644 --- a/cmd/erigon-cl/core/state/ssz.go +++ b/cmd/erigon-cl/core/state/ssz.go @@ -405,7 +405,9 @@ func (b *BeaconState) DecodeSSZWithVersion(buf []byte, version int) error { if b.inactivityScores, err = ssz_utils.DecodeNumbersList(buf, inactivityScoresOffset, endOffset, state_encoding.ValidatorRegistryLimit); err != nil { return err } - + if b.version == clparams.AltairVersion { + return nil + } endOffset = uint32(len(buf)) if historicalSummariesOffset != 0 { endOffset = historicalSummariesOffset diff --git a/cmd/erigon-cl/core/transition/finalization_and_justification.go b/cmd/erigon-cl/core/transition/finalization_and_justification.go index c10cf50e440..dbde836abad 100644 --- a/cmd/erigon-cl/core/transition/finalization_and_justification.go +++ b/cmd/erigon-cl/core/transition/finalization_and_justification.go @@ -62,17 +62,6 @@ func weighJustificationAndFinalization(state *state.BeaconState, previousEpochTa } func ProcessJustificationBitsAndFinality(state *state.BeaconState) error { - if state.Version() == clparams.Phase0Version { - return processJustificationBitsAndFinalityPreAltair(state) - } - return processJustificationBitsAndFinalityAltair(state) -} - -func processJustificationBitsAndFinalityPreAltair(state *state.BeaconState) error { - panic("NOT IMPLEMENTED. STOOOOOP") -} - -func processJustificationBitsAndFinalityAltair(state *state.BeaconState) error { currentEpoch := state.Epoch() previousEpoch := state.PreviousEpoch() beaconConfig := state.BeaconConfig() @@ -80,19 +69,40 @@ func processJustificationBitsAndFinalityAltair(state *state.BeaconState) error { if currentEpoch <= beaconConfig.GenesisEpoch+1 { return nil } - previousParticipation, currentParticipation := state.PreviousEpochParticipation(), state.CurrentEpochParticipation() var previousTargetBalance, currentTargetBalance uint64 - for i, validator := range state.Validators() { - if validator.Slashed { - continue + if state.Version() == clparams.Phase0Version { + // Use attestations to determine the finality. + previousAttestations, err := state.MatchingTargetAttestations(previousEpoch) + if err != nil { + return err } - if validator.Active(previousEpoch) && - previousParticipation[i].HasFlag(int(beaconConfig.TimelyTargetFlagIndex)) { - previousTargetBalance += validator.EffectiveBalance + currentAttestations, err := state.MatchingTargetAttestations(currentEpoch) + if err != nil { + return err + } + previousTargetBalance, err = state.UnslashedAttestingBalance(previousAttestations) + if err != nil { + return err + } + currentTargetBalance, err = state.UnslashedAttestingBalance(currentAttestations) + if err != nil { + return err } - if validator.Active(currentEpoch) && - currentParticipation[i].HasFlag(int(beaconConfig.TimelyTargetFlagIndex)) { - currentTargetBalance += validator.EffectiveBalance + } else { + // Use bitlists to determine finality. + previousParticipation, currentParticipation := state.PreviousEpochParticipation(), state.CurrentEpochParticipation() + for i, validator := range state.Validators() { + if validator.Slashed { + continue + } + if validator.Active(previousEpoch) && + previousParticipation[i].HasFlag(int(beaconConfig.TimelyTargetFlagIndex)) { + previousTargetBalance += validator.EffectiveBalance + } + if validator.Active(currentEpoch) && + currentParticipation[i].HasFlag(int(beaconConfig.TimelyTargetFlagIndex)) { + currentTargetBalance += validator.EffectiveBalance + } } } diff --git a/cmd/erigon-cl/core/transition/process_attestations.go b/cmd/erigon-cl/core/transition/process_attestations.go index 8a3d30704af..386390ba1e0 100644 --- a/cmd/erigon-cl/core/transition/process_attestations.go +++ b/cmd/erigon-cl/core/transition/process_attestations.go @@ -2,8 +2,11 @@ package transition import ( "errors" + "fmt" + "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/utils" "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state" "golang.org/x/exp/slices" ) @@ -32,22 +35,12 @@ func ProcessAttestations(state *state.BeaconState, attestations []*cltypes.Attes return nil } -// ProcessAttestation takes an attestation and process it. -func processAttestation(state *state.BeaconState, attestation *cltypes.Attestation, baseRewardPerIncrement uint64) ([]uint64, error) { +func processAttestationPostAltair(state *state.BeaconState, attestation *cltypes.Attestation, baseRewardPerIncrement uint64) ([]uint64, error) { data := attestation.Data currentEpoch := state.Epoch() - previousEpoch := state.PreviousEpoch() stateSlot := state.Slot() beaconConfig := state.BeaconConfig() - if (data.Target.Epoch != currentEpoch && data.Target.Epoch != previousEpoch) || data.Target.Epoch != state.GetEpochAtSlot(data.Slot) { - return nil, errors.New("ProcessAttestation: attestation with invalid epoch") - } - if data.Slot+beaconConfig.MinAttestationInclusionDelay > stateSlot || stateSlot > data.Slot+beaconConfig.SlotsPerEpoch { - return nil, errors.New("ProcessAttestation: attestation slot not in range") - } - if data.Index >= state.CommitteeCount(data.Target.Epoch) { - return nil, errors.New("ProcessAttestation: attester index out of range") - } + participationFlagsIndicies, err := state.GetAttestationParticipationFlagIndicies(attestation.Data, stateSlot-data.Slot) if err != nil { return nil, err @@ -93,6 +86,68 @@ func processAttestation(state *state.BeaconState, attestation *cltypes.Attestati return attestingIndicies, state.IncreaseBalance(proposer, reward) } +// processAttestationsPhase0 implements the rules for phase0 processing. +func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Attestation) ([]uint64, error) { + data := attestation.Data + committee, err := state.GetBeaconCommitee(data.Slot, data.Index) + if err != nil { + return nil, err + } + + if len(committee) != utils.GetBitlistLength(attestation.AggregationBits) { + return nil, fmt.Errorf("processAttestationPhase0: mismatching aggregation bits size") + } + // Cached so it is performant. + proposerIndex, err := state.GetBeaconProposerIndex() + if err != nil { + return nil, err + } + // Create the attestation to add to pending attestations + pendingAttestation := &cltypes.PendingAttestation{ + Data: data, + AggregationBits: attestation.AggregationBits, + InclusionDelay: state.Slot() - data.Slot, + ProposerIndex: proposerIndex, + } + if data.Target.Epoch == state.Epoch() { + if !data.Source.Equal(state.CurrentJustifiedCheckpoint()) { + return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") + } + state.AddCurrentEpochAtteastation(pendingAttestation) + } else { + if !data.Source.Equal(state.PreviousJustifiedCheckpoint()) { + return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") + } + state.AddPreviousEpochAtteastation(pendingAttestation) + } + + return state.GetAttestingIndicies(attestation.Data, attestation.AggregationBits) +} + +// ProcessAttestation takes an attestation and process it. +func processAttestation(state *state.BeaconState, attestation *cltypes.Attestation, baseRewardPerIncrement uint64) ([]uint64, error) { + data := attestation.Data + currentEpoch := state.Epoch() + previousEpoch := state.PreviousEpoch() + stateSlot := state.Slot() + beaconConfig := state.BeaconConfig() + // Prelimary checks. + if (data.Target.Epoch != currentEpoch && data.Target.Epoch != previousEpoch) || data.Target.Epoch != state.GetEpochAtSlot(data.Slot) { + return nil, errors.New("ProcessAttestation: attestation with invalid epoch") + } + if data.Slot+beaconConfig.MinAttestationInclusionDelay > stateSlot || stateSlot > data.Slot+beaconConfig.SlotsPerEpoch { + return nil, errors.New("ProcessAttestation: attestation slot not in range") + } + if data.Index >= state.CommitteeCount(data.Target.Epoch) { + return nil, errors.New("ProcessAttestation: attester index out of range") + } + // check if we need to use rules for phase0 or post-altair. + if state.Version() == clparams.Phase0Version { + return processAttestationPhase0(state, attestation) + } + return processAttestationPostAltair(state, attestation, baseRewardPerIncrement) +} + type verifyAttestationWorkersResult struct { success bool err error diff --git a/cmd/erigon-cl/core/transition/process_epoch.go b/cmd/erigon-cl/core/transition/process_epoch.go index d6f0f8d4b72..7baed9c338c 100644 --- a/cmd/erigon-cl/core/transition/process_epoch.go +++ b/cmd/erigon-cl/core/transition/process_epoch.go @@ -34,9 +34,7 @@ func ProcessEpoch(state *state.BeaconState) error { return err } if state.Version() == clparams.Phase0Version { - if err := ProcessParticipationRecordUpdates(state); err != nil { - return err - } + ProcessParticipationRecordUpdates(state) } if state.Version() >= clparams.AltairVersion { @@ -75,6 +73,7 @@ func ProcessEpoch(state *state.BeaconState) error { */ } -func ProcessParticipationRecordUpdates(state *state.BeaconState) error { - panic("not implemented") +func ProcessParticipationRecordUpdates(state *state.BeaconState) { + state.SetPreviousEpochAtteastations(state.CurrentEpochAttestations()) + state.SetCurrentEpochAtteastations(nil) } From c390c73e6bdb0183abe8b0b401e17acb840fb615 Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Fri, 10 Mar 2023 13:49:58 +0100 Subject: [PATCH 2/7] added process rewards and penalties but missing inclusion delay case --- cl/cltypes/validator.go | 29 ++++- cmd/erigon-cl/core/state/accessors.go | 25 ++++- cmd/erigon-cl/core/state/state.go | 66 +++++++++++ .../finalization_and_justification.go | 26 ++--- .../core/transition/process_attestations.go | 50 ++++++++- .../core/transition/process_epoch.go | 49 ++++----- .../process_rewards_and_penalties.go | 103 +++++++++++++++++- 7 files changed, 290 insertions(+), 58 deletions(-) diff --git a/cl/cltypes/validator.go b/cl/cltypes/validator.go index 87c086b42e9..12d61539722 100644 --- a/cl/cltypes/validator.go +++ b/cl/cltypes/validator.go @@ -294,8 +294,35 @@ type Validator struct { ActivationEpoch uint64 ExitEpoch uint64 WithdrawableEpoch uint64 + // This is all stuff used by phase0 state transition. It makes many operations faster. + // Source attesters + IsCurrentMatchingSourceAttester bool + IsPreviousMatchingSourceAttester bool + // Target Attesters + IsCurrentMatchingTargetAttester bool + IsPreviousMatchingTargetAttester bool + // Head attesters + IsCurrentMatchingHeadAttester bool + IsPreviousMatchingHeadAttester bool +} + +// DutiesAttested returns how many of its duties the validator attested and missed +func (v *Validator) DutiesAttested() (attested, missed uint64) { + if v.Slashed { + return 0, 3 + } + if v.IsPreviousMatchingSourceAttester { + attested++ + } + if v.IsPreviousMatchingTargetAttester { + attested++ + } + if v.IsPreviousMatchingHeadAttester { + attested++ + } + missed = 3 - attested + return } - func (v *Validator) IsSlashable(epoch uint64) bool { return !v.Slashed && (v.ActivationEpoch <= epoch) && (epoch < v.WithdrawableEpoch) } diff --git a/cmd/erigon-cl/core/state/accessors.go b/cmd/erigon-cl/core/state/accessors.go index 92f24b9ab99..8b6c663c05d 100644 --- a/cmd/erigon-cl/core/state/accessors.go +++ b/cmd/erigon-cl/core/state/accessors.go @@ -2,6 +2,7 @@ package state import ( "encoding/binary" + "errors" "fmt" "sort" @@ -16,6 +17,10 @@ import ( const PreAllocatedRewardsAndPenalties = 8192 +var ( + ErrGetBlockRootAtSlotFuture = errors.New("GetBlockRootAtSlot: slot in the future") +) + // GetActiveValidatorsIndices returns the list of validator indices active for the given epoch. func (b *BeaconState) GetActiveValidatorsIndices(epoch uint64) (indicies []uint64) { if cachedIndicies, ok := b.activeValidatorsCache.Get(epoch); ok { @@ -118,7 +123,7 @@ func (b *BeaconState) GetBlockRoot(epoch uint64) (libcommon.Hash, error) { // GetBlockRootAtSlot returns the block root at a given slot func (b *BeaconState) GetBlockRootAtSlot(slot uint64) (libcommon.Hash, error) { if slot >= b.slot { - return libcommon.Hash{}, fmt.Errorf("GetBlockRootAtSlot: slot in the future") + return libcommon.Hash{}, ErrGetBlockRootAtSlotFuture } if b.slot > slot+b.beaconConfig.SlotsPerHistoricalRoot { return libcommon.Hash{}, fmt.Errorf("GetBlockRootAtSlot: slot too much far behind") @@ -384,13 +389,16 @@ func (b *BeaconState) GetIndexedAttestation(attestation *cltypes.Attestation, at }, nil } -func (b *BeaconState) GetAttestingIndicies(attestation *cltypes.AttestationData, aggregationBits []byte) ([]uint64, error) { +// GetAttestingIndicies retrieves attesting indicies for a specific attestation. however some tests will not expect the aggregation bits check. +// thus, it is a flag now. +func (b *BeaconState) GetAttestingIndicies(attestation *cltypes.AttestationData, aggregationBits []byte, checkBitsLength bool) ([]uint64, error) { committee, err := b.GetBeaconCommitee(attestation.Slot, attestation.Index) if err != nil { return nil, err } - if utils.GetBitlistLength(aggregationBits) != len(committee) { - return nil, fmt.Errorf("GetAttestingIndicies: invalid aggregation bits") + aggregationBitsLen := utils.GetBitlistLength(aggregationBits) + if checkBitsLength && utils.GetBitlistLength(aggregationBits) != len(committee) { + return nil, fmt.Errorf("GetAttestingIndicies: invalid aggregation bits. agg bits size: %d, expect: %d", aggregationBitsLen, len(committee)) } attestingIndices := []uint64{} for i, member := range committee { @@ -419,9 +427,14 @@ func (b *BeaconState) EligibleValidatorsIndicies() (eligibleValidators []uint64) return } +// FinalityDelay determines by how many epochs we are late on finality. +func (b *BeaconState) FinalityDelay() uint64 { + return b.PreviousEpoch() - b.finalizedCheckpoint.Epoch +} + // Implementation of is_in_inactivity_leak. tells us if network is in danger pretty much. defined in ETH 2.0 specs. func (b *BeaconState) InactivityLeaking() bool { - return (b.PreviousEpoch() - b.finalizedCheckpoint.Epoch) > b.beaconConfig.MinEpochsToInactivityPenalty + return b.FinalityDelay() > b.beaconConfig.MinEpochsToInactivityPenalty } func (b *BeaconState) IsUnslashedParticipatingIndex(epoch, index uint64, flagIdx int) bool { @@ -564,7 +577,7 @@ func (b *BeaconState) MatchingTargetAttestations(epoch uint64) ([]*cltypes.Pendi func (b *BeaconState) UnslashedAttestingBalance(attestations []*cltypes.PendingAttestation) (uint64, error) { sum := uint64(0) for _, attestation := range attestations { - attestingIndicies, err := b.GetAttestingIndicies(attestation.Data, attestation.AggregationBits) + attestingIndicies, err := b.GetAttestingIndicies(attestation.Data, attestation.AggregationBits, true) if err != nil { return 0, err } diff --git a/cmd/erigon-cl/core/state/state.go b/cmd/erigon-cl/core/state/state.go index 2df5df8a513..3b9721c0890 100644 --- a/cmd/erigon-cl/core/state/state.go +++ b/cmd/erigon-cl/core/state/state.go @@ -149,6 +149,69 @@ func (b *BeaconState) _updateProposerIndex() (err error) { return } +// _initializeValidatorsPhase0 initializes the validators matching flags based on previous/current attestations +func (b *BeaconState) _initializeValidatorsPhase0() error { + // Previous Pending attestations + if b.slot == 0 { + return nil + } + previousEpochRoot, err := b.GetBlockRoot(b.PreviousEpoch()) + if err != nil { + return err + } + for _, attestation := range b.previousEpochAttestations { + slotRoot, err := b.GetBlockRootAtSlot(attestation.Data.Slot) + if err != nil { + return err + } + indicies, err := b.GetAttestingIndicies(attestation.Data, attestation.AggregationBits, false) + if err != nil { + return err + } + for _, index := range indicies { + b.validators[index].IsPreviousMatchingSourceAttester = true + if attestation.Data.Target.Root == previousEpochRoot { + b.validators[index].IsPreviousMatchingTargetAttester = true + } + if attestation.Data.BeaconBlockHash == slotRoot { + b.validators[index].IsPreviousMatchingHeadAttester = true + } + } + } + + // Current Pending attestations + if len(b.currentEpochAttestations) == 0 { + return nil + } + currentEpochRoot, err := b.GetBlockRoot(b.Epoch()) + if err != nil { + return err + } + for _, attestation := range b.currentEpochAttestations { + slotRoot, err := b.GetBlockRootAtSlot(attestation.Data.Slot) + if err != nil { + return err + } + if err != nil { + return err + } + indicies, err := b.GetAttestingIndicies(attestation.Data, attestation.AggregationBits, false) + if err != nil { + return err + } + for _, index := range indicies { + b.validators[index].IsCurrentMatchingSourceAttester = true + if attestation.Data.Target.Root == currentEpochRoot { + b.validators[index].IsCurrentMatchingTargetAttester = true + } + if attestation.Data.BeaconBlockHash == slotRoot { + b.validators[index].IsCurrentMatchingHeadAttester = true + } + } + } + return nil +} + func (b *BeaconState) initBeaconState() error { if b.touchedLeaves == nil { b.touchedLeaves = make(map[StateLeafIndex]bool) @@ -171,5 +234,8 @@ func (b *BeaconState) initBeaconState() error { if err := b._updateProposerIndex(); err != nil { return err } + if b.version >= clparams.Phase0Version { + return b._initializeValidatorsPhase0() + } return nil } diff --git a/cmd/erigon-cl/core/transition/finalization_and_justification.go b/cmd/erigon-cl/core/transition/finalization_and_justification.go index dbde836abad..5d4a9e11364 100644 --- a/cmd/erigon-cl/core/transition/finalization_and_justification.go +++ b/cmd/erigon-cl/core/transition/finalization_and_justification.go @@ -71,22 +71,16 @@ func ProcessJustificationBitsAndFinality(state *state.BeaconState) error { } var previousTargetBalance, currentTargetBalance uint64 if state.Version() == clparams.Phase0Version { - // Use attestations to determine the finality. - previousAttestations, err := state.MatchingTargetAttestations(previousEpoch) - if err != nil { - return err - } - currentAttestations, err := state.MatchingTargetAttestations(currentEpoch) - if err != nil { - return err - } - previousTargetBalance, err = state.UnslashedAttestingBalance(previousAttestations) - if err != nil { - return err - } - currentTargetBalance, err = state.UnslashedAttestingBalance(currentAttestations) - if err != nil { - return err + for _, validator := range state.Validators() { + if validator.Slashed { + continue + } + if validator.IsCurrentMatchingTargetAttester { + currentTargetBalance += validator.EffectiveBalance + } + if validator.IsPreviousMatchingTargetAttester { + previousTargetBalance += validator.EffectiveBalance + } } } else { // Use bitlists to determine finality. diff --git a/cmd/erigon-cl/core/transition/process_attestations.go b/cmd/erigon-cl/core/transition/process_attestations.go index 386390ba1e0..86406a4828d 100644 --- a/cmd/erigon-cl/core/transition/process_attestations.go +++ b/cmd/erigon-cl/core/transition/process_attestations.go @@ -46,7 +46,7 @@ func processAttestationPostAltair(state *state.BeaconState, attestation *cltypes return nil, err } - attestingIndicies, err := state.GetAttestingIndicies(attestation.Data, attestation.AggregationBits) + attestingIndicies, err := state.GetAttestingIndicies(attestation.Data, attestation.AggregationBits, true) if err != nil { return nil, err } @@ -109,7 +109,9 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att InclusionDelay: state.Slot() - data.Slot, ProposerIndex: proposerIndex, } - if data.Target.Epoch == state.Epoch() { + isCurrentAttestation := data.Target.Epoch == state.Epoch() + // Depending of what slot we are on we put in either the current justified or previous justified. + if isCurrentAttestation { if !data.Source.Equal(state.CurrentJustifiedCheckpoint()) { return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") } @@ -120,8 +122,50 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att } state.AddPreviousEpochAtteastation(pendingAttestation) } + // Not required by specs but needed if we want performant epoch transition. + indicies, err := state.GetAttestingIndicies(attestation.Data, attestation.AggregationBits, true) + if err != nil { + return nil, err + } + epochRoot, err := state.GetBlockRoot(attestation.Data.Target.Epoch) + if err != nil { + return nil, err + } + slotRoot, err := state.GetBlockRootAtSlot(attestation.Data.Slot) + if err != nil { + return nil, err + } + // Basically we flag all validators we are currently attesting. will be important for rewards/finalization processing. + for _, index := range indicies { + validator, err := state.ValidatorAt(int(index)) + if err != nil { + return nil, err + } + // NOTE: does not affect state root. + // We need to set it to currents or previouses depending on which attestation we process. + if isCurrentAttestation { + validator.IsCurrentMatchingSourceAttester = true + if attestation.Data.Target.Root == epochRoot { + validator.IsCurrentMatchingTargetAttester = true + } + if attestation.Data.BeaconBlockHash == slotRoot { + validator.IsCurrentMatchingHeadAttester = true + } + } else { + validator.IsPreviousMatchingSourceAttester = true + if attestation.Data.Target.Root == epochRoot { + validator.IsPreviousMatchingTargetAttester = true + } + if attestation.Data.BeaconBlockHash == slotRoot { + validator.IsPreviousMatchingHeadAttester = true + } + } - return state.GetAttestingIndicies(attestation.Data, attestation.AggregationBits) + if err := state.SetValidatorAt(int(index), validator); err != nil { + return nil, err + } + } + return indicies, nil } // ProcessAttestation takes an attestation and process it. diff --git a/cmd/erigon-cl/core/transition/process_epoch.go b/cmd/erigon-cl/core/transition/process_epoch.go index 7baed9c338c..2c1fe95c29b 100644 --- a/cmd/erigon-cl/core/transition/process_epoch.go +++ b/cmd/erigon-cl/core/transition/process_epoch.go @@ -34,7 +34,9 @@ func ProcessEpoch(state *state.BeaconState) error { return err } if state.Version() == clparams.Phase0Version { - ProcessParticipationRecordUpdates(state) + if err := ProcessParticipationRecordUpdates(state); err != nil { + return err + } } if state.Version() >= clparams.AltairVersion { @@ -44,36 +46,25 @@ func ProcessEpoch(state *state.BeaconState) error { } } return nil - - /*def process_epoch(state: BeaconState) -> None: - process_justification_and_finalization(state) - process_rewards_and_penalties(state) - process_registry_updates(state) - process_slashings(state) - process_eth1_data_reset(state) - process_effective_balance_updates(state) - process_slashings_reset(state) - process_randao_mixes_reset(state) - process_historical_roots_update(state) - process_participation_record_updates(state)*/ - /* - def process_epoch(state: BeaconState) -> None: - process_justification_and_finalization(state) # [Modified in Altair] - process_inactivity_updates(state) # [New in Altair] - process_rewards_and_penalties(state) # [Modified in Altair] - process_registry_updates(state) - process_slashings(state) # [Modified in Altair] - process_eth1_data_reset(state) - process_effective_balance_updates(state) - process_slashings_reset(state) - process_randao_mixes_reset(state) - process_historical_roots_update(state) - process_participation_flag_updates(state) # [New in Altair] - process_sync_committee_updates(state) # [New in Altair] - */ } -func ProcessParticipationRecordUpdates(state *state.BeaconState) { +func ProcessParticipationRecordUpdates(state *state.BeaconState) error { state.SetPreviousEpochAtteastations(state.CurrentEpochAttestations()) state.SetCurrentEpochAtteastations(nil) + // Also mark all current attesters as previous + for validatorIndex, validator := range state.Validators() { + // Previous sources/target/head + validator.IsPreviousMatchingSourceAttester = validator.IsCurrentMatchingSourceAttester + validator.IsPreviousMatchingTargetAttester = validator.IsCurrentMatchingTargetAttester + validator.IsPreviousMatchingHeadAttester = validator.IsCurrentMatchingHeadAttester + // Current sources/target/head + validator.IsCurrentMatchingSourceAttester = false + validator.IsCurrentMatchingTargetAttester = false + validator.IsCurrentMatchingHeadAttester = false + // Setting the validator + if err := state.SetValidatorAt(int(validatorIndex), validator); err != nil { + return err + } + } + return nil } diff --git a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go index 6dd9e8d5dd2..563f61402fb 100644 --- a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go +++ b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go @@ -1,9 +1,11 @@ package transition -import "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state" +import ( + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state" +) -// ProcessRewardsAndPenalties applies rewards/penalties accumulated during previous epoch. -func ProcessRewardsAndPenalties(state *state.BeaconState) (err error) { +func processRewardsAndPenaltiesPostAltair(state *state.BeaconState) (err error) { beaconConfig := state.BeaconConfig() weights := beaconConfig.ParticipationWeights() if state.Epoch() == beaconConfig.GenesisEpoch { @@ -64,3 +66,98 @@ func ProcessRewardsAndPenalties(state *state.BeaconState) (err error) { } return } + +func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { + beaconConfig := state.BeaconConfig() + if state.Epoch() == beaconConfig.GenesisEpoch { + return nil + } + eligibleValidators := state.EligibleValidatorsIndicies() + // Initialize variables + rewardDenominator := state.GetTotalActiveBalance() / beaconConfig.EffectiveBalanceIncrement + validators := state.Validators() + // Make buffer for flag indexes totTargetal balances. + var unslashedMatchingSourceBalance, unslashedMatchingTargetBalance, unslashedMatchingHeadBalance uint64 + // Compute all total balances for each enable unslashed validator indicies with all flags on. + for _, validator := range state.Validators() { + if validator.Slashed { + continue + } + if validator.IsPreviousMatchingSourceAttester { + unslashedMatchingSourceBalance += validator.EffectiveBalance + } + if validator.IsPreviousMatchingTargetAttester { + unslashedMatchingTargetBalance += validator.EffectiveBalance + } + if validator.IsPreviousMatchingHeadAttester { + unslashedMatchingHeadBalance += validator.EffectiveBalance + } + } + // Then compute their total increment. + unslashedMatchingSourceBalance /= beaconConfig.EffectiveBalanceIncrement + unslashedMatchingTargetBalance /= beaconConfig.EffectiveBalanceIncrement + unslashedMatchingHeadBalance /= beaconConfig.EffectiveBalanceIncrement + // precompute partially the base reward. + baseRewardIncrement := state.BaseRewardPerIncrement() + // Now process deltas and whats nots. + for _, index := range eligibleValidators { + baseReward := (validators[index].EffectiveBalance / beaconConfig.EffectiveBalanceIncrement) * baseRewardIncrement + // we can use a multiplier to account for all attesting + attested, missed := validators[index].DutiesAttested() + // If we attested then we reward the validator. + if attested > 0 { + if state.InactivityLeaking() { + if err := state.IncreaseBalance(index, baseReward*attested); err != nil { + return err + } + } else { + if !validators[index].Slashed && validators[index].IsPreviousMatchingSourceAttester { + rewardNumerator := baseReward * unslashedMatchingSourceBalance + if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { + return err + } + } + if !validators[index].Slashed && validators[index].IsPreviousMatchingTargetAttester { + rewardNumerator := baseReward * unslashedMatchingTargetBalance + if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { + return err + } + } + if !validators[index].Slashed && validators[index].IsPreviousMatchingHeadAttester { + rewardNumerator := baseReward * unslashedMatchingHeadBalance + if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { + return err + } + } + } + // Process inactivity of the network as a whole finalities. + if state.InactivityLeaking() { + // Neutralize rewards. + if state.DecreaseBalance(index, beaconConfig.BaseRewardsPerEpoch*baseReward*(baseReward/beaconConfig.ProposerRewardQuotient)); err != nil { + return err + } + if validators[index].Slashed || validators[index].IsPreviousMatchingTargetAttester { + // Increase penalities linearly if network is leaking. + if state.DecreaseBalance(index, validators[index].EffectiveBalance*state.FinalityDelay()/beaconConfig.InactivityPenaltyQuotient); err != nil { + return err + } + } + } + } + + // For each missed duty we penalize the validator. + if state.DecreaseBalance(index, baseReward*missed); err != nil { + return err + } + + } + return +} + +// ProcessRewardsAndPenalties applies rewards/penalties accumulated during previous epoch. +func ProcessRewardsAndPenalties(state *state.BeaconState) error { + if state.Version() == clparams.Phase0Version { + return nil + } + return processRewardsAndPenaltiesPostAltair(state) +} From d28d73bb37ae66036e1682dfa358cc3334aa02ea Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Fri, 10 Mar 2023 14:43:46 +0100 Subject: [PATCH 3/7] done --- cl/cltypes/validator.go | 3 +++ .../consensus_tests/epoch_processing.go | 3 +-- cmd/erigon-cl/core/state/state.go | 6 +++++ .../core/transition/process_attestations.go | 6 +++++ .../core/transition/process_epoch.go | 2 ++ .../process_rewards_and_penalties.go | 25 +++++++++++++++++-- 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/cl/cltypes/validator.go b/cl/cltypes/validator.go index 12d61539722..83c45e396cd 100644 --- a/cl/cltypes/validator.go +++ b/cl/cltypes/validator.go @@ -304,6 +304,9 @@ type Validator struct { // Head attesters IsCurrentMatchingHeadAttester bool IsPreviousMatchingHeadAttester bool + // MinInclusionDelay + MinCurrentInclusionDelayAttestation *PendingAttestation + MinPreviousInclusionDelayAttestation *PendingAttestation } // DutiesAttested returns how many of its duties the validator attested and missed diff --git a/cmd/ef-tests-cl/consensus_tests/epoch_processing.go b/cmd/ef-tests-cl/consensus_tests/epoch_processing.go index f1f7993bf87..23cddb1b82b 100644 --- a/cmd/ef-tests-cl/consensus_tests/epoch_processing.go +++ b/cmd/ef-tests-cl/consensus_tests/epoch_processing.go @@ -29,10 +29,9 @@ func getTestEpochProcessing(f func(s *state.BeaconState) error) testFunc { expectedState, err := decodeStateFromFile(context, "post.ssz_snappy") if os.IsNotExist(err) { isErrExpected = true - } else { + } else if err != nil { return err } - // Make up state transistor if err := f(testState); err != nil { if isErrExpected { diff --git a/cmd/erigon-cl/core/state/state.go b/cmd/erigon-cl/core/state/state.go index 3b9721c0890..69b56d0d4dd 100644 --- a/cmd/erigon-cl/core/state/state.go +++ b/cmd/erigon-cl/core/state/state.go @@ -169,6 +169,9 @@ func (b *BeaconState) _initializeValidatorsPhase0() error { return err } for _, index := range indicies { + if b.validators[index].MinPreviousInclusionDelayAttestation == nil || b.validators[index].MinPreviousInclusionDelayAttestation.InclusionDelay > attestation.InclusionDelay { + b.validators[index].MinPreviousInclusionDelayAttestation = attestation + } b.validators[index].IsPreviousMatchingSourceAttester = true if attestation.Data.Target.Root == previousEpochRoot { b.validators[index].IsPreviousMatchingTargetAttester = true @@ -200,6 +203,9 @@ func (b *BeaconState) _initializeValidatorsPhase0() error { return err } for _, index := range indicies { + if b.validators[index].MinCurrentInclusionDelayAttestation == nil || b.validators[index].MinCurrentInclusionDelayAttestation.InclusionDelay > attestation.InclusionDelay { + b.validators[index].MinCurrentInclusionDelayAttestation = attestation + } b.validators[index].IsCurrentMatchingSourceAttester = true if attestation.Data.Target.Root == currentEpochRoot { b.validators[index].IsCurrentMatchingTargetAttester = true diff --git a/cmd/erigon-cl/core/transition/process_attestations.go b/cmd/erigon-cl/core/transition/process_attestations.go index 86406a4828d..0cedf1f6f77 100644 --- a/cmd/erigon-cl/core/transition/process_attestations.go +++ b/cmd/erigon-cl/core/transition/process_attestations.go @@ -144,6 +144,9 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att // NOTE: does not affect state root. // We need to set it to currents or previouses depending on which attestation we process. if isCurrentAttestation { + if validator.MinCurrentInclusionDelayAttestation == nil || validator.MinCurrentInclusionDelayAttestation.InclusionDelay > pendingAttestation.InclusionDelay { + validator.MinCurrentInclusionDelayAttestation = pendingAttestation + } validator.IsCurrentMatchingSourceAttester = true if attestation.Data.Target.Root == epochRoot { validator.IsCurrentMatchingTargetAttester = true @@ -152,6 +155,9 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att validator.IsCurrentMatchingHeadAttester = true } } else { + if validator.MinPreviousInclusionDelayAttestation == nil || validator.MinPreviousInclusionDelayAttestation.InclusionDelay > pendingAttestation.InclusionDelay { + validator.MinPreviousInclusionDelayAttestation = pendingAttestation + } validator.IsPreviousMatchingSourceAttester = true if attestation.Data.Target.Root == epochRoot { validator.IsPreviousMatchingTargetAttester = true diff --git a/cmd/erigon-cl/core/transition/process_epoch.go b/cmd/erigon-cl/core/transition/process_epoch.go index 2c1fe95c29b..f8e7169431d 100644 --- a/cmd/erigon-cl/core/transition/process_epoch.go +++ b/cmd/erigon-cl/core/transition/process_epoch.go @@ -57,7 +57,9 @@ func ProcessParticipationRecordUpdates(state *state.BeaconState) error { validator.IsPreviousMatchingSourceAttester = validator.IsCurrentMatchingSourceAttester validator.IsPreviousMatchingTargetAttester = validator.IsCurrentMatchingTargetAttester validator.IsPreviousMatchingHeadAttester = validator.IsCurrentMatchingHeadAttester + validator.MinPreviousInclusionDelayAttestation = validator.MinCurrentInclusionDelayAttestation // Current sources/target/head + validator.MinCurrentInclusionDelayAttestation = nil validator.IsCurrentMatchingSourceAttester = false validator.IsCurrentMatchingTargetAttester = false validator.IsCurrentMatchingHeadAttester = false diff --git a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go index 563f61402fb..885a9b9f370 100644 --- a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go +++ b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go @@ -132,8 +132,9 @@ func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { } // Process inactivity of the network as a whole finalities. if state.InactivityLeaking() { + proposerReward := (baseReward / beaconConfig.ProposerRewardQuotient) // Neutralize rewards. - if state.DecreaseBalance(index, beaconConfig.BaseRewardsPerEpoch*baseReward*(baseReward/beaconConfig.ProposerRewardQuotient)); err != nil { + if state.DecreaseBalance(index, beaconConfig.BaseRewardsPerEpoch*baseReward*proposerReward); err != nil { return err } if validators[index].Slashed || validators[index].IsPreviousMatchingTargetAttester { @@ -151,13 +152,33 @@ func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { } } + // Lastly process late attestations + for index, validator := range validators { + if validator.Slashed || !validator.IsPreviousMatchingSourceAttester { + continue + } + attestation := validators[index].MinPreviousInclusionDelayAttestation + baseReward, err := state.BaseReward(uint64(index)) + if err != nil { + return err + } + // Compute proposer reward. + proposerReward := (baseReward / beaconConfig.ProposerRewardQuotient) + if err := state.IncreaseBalance(attestation.ProposerIndex, proposerReward); err != nil { + return err + } + maxAttesterReward := baseReward - proposerReward + if err := state.IncreaseBalance(uint64(index), maxAttesterReward/attestation.InclusionDelay); err != nil { + return err + } + } return } // ProcessRewardsAndPenalties applies rewards/penalties accumulated during previous epoch. func ProcessRewardsAndPenalties(state *state.BeaconState) error { if state.Version() == clparams.Phase0Version { - return nil + return processRewardsAndPenaltiesPhase0(state) } return processRewardsAndPenaltiesPostAltair(state) } From 52bb12fbaf2865f8ab96f95ce1e4df61b80f8105 Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Fri, 10 Mar 2023 23:08:27 +0100 Subject: [PATCH 4/7] fixed up rewards and penalties a little --- cmd/erigon-cl/core/state/accessors.go | 8 ++++- .../process_rewards_and_penalties.go | 33 ++++++++++--------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cmd/erigon-cl/core/state/accessors.go b/cmd/erigon-cl/core/state/accessors.go index 8b6c663c05d..35c49a3ebbc 100644 --- a/cmd/erigon-cl/core/state/accessors.go +++ b/cmd/erigon-cl/core/state/accessors.go @@ -272,7 +272,13 @@ func (b *BeaconState) BaseReward(index uint64) (uint64, error) { if index >= uint64(len(b.validators)) { return 0, ErrInvalidValidatorIndex } - return (b.validators[index].EffectiveBalance / b.beaconConfig.EffectiveBalanceIncrement) * b.BaseRewardPerIncrement(), nil + if b.totalActiveBalanceCache == nil { + b._refreshActiveBalances() + } + if b.version != clparams.Phase0Version { + return (b.validators[index].EffectiveBalance / b.beaconConfig.EffectiveBalanceIncrement) * b.BaseRewardPerIncrement(), nil + } + return b.validators[index].EffectiveBalance * b.beaconConfig.BaseRewardFactor / b.totalActiveBalanceRootCache / b.beaconConfig.BaseRewardsPerEpoch, nil } // SyncRewards returns the proposer reward and the sync participant reward given the total active balance in state. diff --git a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go index 885a9b9f370..8f7d419ba5b 100644 --- a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go +++ b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go @@ -77,31 +77,32 @@ func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { rewardDenominator := state.GetTotalActiveBalance() / beaconConfig.EffectiveBalanceIncrement validators := state.Validators() // Make buffer for flag indexes totTargetal balances. - var unslashedMatchingSourceBalance, unslashedMatchingTargetBalance, unslashedMatchingHeadBalance uint64 + var unslashedMatchingSourceBalanceIncrements, unslashedMatchingTargetBalanceIncrements, unslashedMatchingHeadBalanceIncrements uint64 // Compute all total balances for each enable unslashed validator indicies with all flags on. for _, validator := range state.Validators() { if validator.Slashed { continue } if validator.IsPreviousMatchingSourceAttester { - unslashedMatchingSourceBalance += validator.EffectiveBalance + unslashedMatchingSourceBalanceIncrements += validator.EffectiveBalance } if validator.IsPreviousMatchingTargetAttester { - unslashedMatchingTargetBalance += validator.EffectiveBalance + unslashedMatchingTargetBalanceIncrements += validator.EffectiveBalance } if validator.IsPreviousMatchingHeadAttester { - unslashedMatchingHeadBalance += validator.EffectiveBalance + unslashedMatchingHeadBalanceIncrements += validator.EffectiveBalance } } // Then compute their total increment. - unslashedMatchingSourceBalance /= beaconConfig.EffectiveBalanceIncrement - unslashedMatchingTargetBalance /= beaconConfig.EffectiveBalanceIncrement - unslashedMatchingHeadBalance /= beaconConfig.EffectiveBalanceIncrement - // precompute partially the base reward. - baseRewardIncrement := state.BaseRewardPerIncrement() + unslashedMatchingSourceBalanceIncrements /= beaconConfig.EffectiveBalanceIncrement + unslashedMatchingTargetBalanceIncrements /= beaconConfig.EffectiveBalanceIncrement + unslashedMatchingHeadBalanceIncrements /= beaconConfig.EffectiveBalanceIncrement // Now process deltas and whats nots. for _, index := range eligibleValidators { - baseReward := (validators[index].EffectiveBalance / beaconConfig.EffectiveBalanceIncrement) * baseRewardIncrement + baseReward, err := state.BaseReward(index) + if err != nil { + return err + } // we can use a multiplier to account for all attesting attested, missed := validators[index].DutiesAttested() // If we attested then we reward the validator. @@ -112,19 +113,19 @@ func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { } } else { if !validators[index].Slashed && validators[index].IsPreviousMatchingSourceAttester { - rewardNumerator := baseReward * unslashedMatchingSourceBalance + rewardNumerator := baseReward * unslashedMatchingSourceBalanceIncrements if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { return err } } if !validators[index].Slashed && validators[index].IsPreviousMatchingTargetAttester { - rewardNumerator := baseReward * unslashedMatchingTargetBalance + rewardNumerator := baseReward * unslashedMatchingTargetBalanceIncrements if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { return err } } if !validators[index].Slashed && validators[index].IsPreviousMatchingHeadAttester { - rewardNumerator := baseReward * unslashedMatchingHeadBalance + rewardNumerator := baseReward * unslashedMatchingHeadBalanceIncrements if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { return err } @@ -132,12 +133,12 @@ func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { } // Process inactivity of the network as a whole finalities. if state.InactivityLeaking() { - proposerReward := (baseReward / beaconConfig.ProposerRewardQuotient) + proposerReward := baseReward / beaconConfig.ProposerRewardQuotient // Neutralize rewards. - if state.DecreaseBalance(index, beaconConfig.BaseRewardsPerEpoch*baseReward*proposerReward); err != nil { + if state.DecreaseBalance(index, beaconConfig.BaseRewardsPerEpoch*baseReward-proposerReward); err != nil { return err } - if validators[index].Slashed || validators[index].IsPreviousMatchingTargetAttester { + if validators[index].Slashed || !validators[index].IsPreviousMatchingTargetAttester { // Increase penalities linearly if network is leaking. if state.DecreaseBalance(index, validators[index].EffectiveBalance*state.FinalityDelay()/beaconConfig.InactivityPenaltyQuotient); err != nil { return err From 42be9f90cfa3a6eaaec321d33ed220da3d06f450 Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Fri, 10 Mar 2023 23:44:45 +0100 Subject: [PATCH 5/7] 1 test remaining --- .../process_rewards_and_penalties.go | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go index 8f7d419ba5b..81e9567fdaf 100644 --- a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go +++ b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go @@ -67,6 +67,7 @@ func processRewardsAndPenaltiesPostAltair(state *state.BeaconState) (err error) return } +// processRewardsAndPenaltiesPhase0 process rewards and penalties for phase0 state. func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { beaconConfig := state.BeaconConfig() if state.Epoch() == beaconConfig.GenesisEpoch { @@ -106,43 +107,41 @@ func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { // we can use a multiplier to account for all attesting attested, missed := validators[index].DutiesAttested() // If we attested then we reward the validator. - if attested > 0 { - if state.InactivityLeaking() { - if err := state.IncreaseBalance(index, baseReward*attested); err != nil { + if state.InactivityLeaking() { + if err := state.IncreaseBalance(index, baseReward*attested); err != nil { + return err + } + } else { + if !validators[index].Slashed && validators[index].IsPreviousMatchingSourceAttester { + rewardNumerator := baseReward * unslashedMatchingSourceBalanceIncrements + if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { return err } - } else { - if !validators[index].Slashed && validators[index].IsPreviousMatchingSourceAttester { - rewardNumerator := baseReward * unslashedMatchingSourceBalanceIncrements - if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { - return err - } - } - if !validators[index].Slashed && validators[index].IsPreviousMatchingTargetAttester { - rewardNumerator := baseReward * unslashedMatchingTargetBalanceIncrements - if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { - return err - } - } - if !validators[index].Slashed && validators[index].IsPreviousMatchingHeadAttester { - rewardNumerator := baseReward * unslashedMatchingHeadBalanceIncrements - if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { - return err - } + } + if !validators[index].Slashed && validators[index].IsPreviousMatchingTargetAttester { + rewardNumerator := baseReward * unslashedMatchingTargetBalanceIncrements + if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { + return err } } - // Process inactivity of the network as a whole finalities. - if state.InactivityLeaking() { - proposerReward := baseReward / beaconConfig.ProposerRewardQuotient - // Neutralize rewards. - if state.DecreaseBalance(index, beaconConfig.BaseRewardsPerEpoch*baseReward-proposerReward); err != nil { + if !validators[index].Slashed && validators[index].IsPreviousMatchingHeadAttester { + rewardNumerator := baseReward * unslashedMatchingHeadBalanceIncrements + if err := state.IncreaseBalance(index, rewardNumerator/rewardDenominator); err != nil { return err } - if validators[index].Slashed || !validators[index].IsPreviousMatchingTargetAttester { - // Increase penalities linearly if network is leaking. - if state.DecreaseBalance(index, validators[index].EffectiveBalance*state.FinalityDelay()/beaconConfig.InactivityPenaltyQuotient); err != nil { - return err - } + } + } + // Process inactivity of the network as a whole finalities. + if state.InactivityLeaking() { + proposerReward := baseReward / beaconConfig.ProposerRewardQuotient + // Neutralize rewards. + if state.DecreaseBalance(index, beaconConfig.BaseRewardsPerEpoch*baseReward-proposerReward); err != nil { + return err + } + if validators[index].Slashed || !validators[index].IsPreviousMatchingTargetAttester { + // Increase penalities linearly if network is leaking. + if state.DecreaseBalance(index, validators[index].EffectiveBalance*state.FinalityDelay()/beaconConfig.InactivityPenaltyQuotient); err != nil { + return err } } } From cd86fb050b6fe516f442f0e4ae0f3d8b36354fae Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Sat, 11 Mar 2023 18:19:41 +0100 Subject: [PATCH 6/7] finished implementing phase0 --- .../consensus_tests/epoch_processing.go | 1 - cmd/ef-tests-cl/consensus_tests/sanity.go | 8 ++- cmd/erigon-cl/core/state/accessors.go | 56 ------------------- cmd/erigon-cl/core/state/state.go | 6 +- .../core/transition/process_attestations.go | 7 ++- .../process_rewards_and_penalties.go | 6 +- 6 files changed, 18 insertions(+), 66 deletions(-) diff --git a/cmd/ef-tests-cl/consensus_tests/epoch_processing.go b/cmd/ef-tests-cl/consensus_tests/epoch_processing.go index 23cddb1b82b..72eb4591d42 100644 --- a/cmd/ef-tests-cl/consensus_tests/epoch_processing.go +++ b/cmd/ef-tests-cl/consensus_tests/epoch_processing.go @@ -39,7 +39,6 @@ func getTestEpochProcessing(f func(s *state.BeaconState) error) testFunc { } return err } - if isErrExpected && err == nil { return fmt.Errorf("expected an error got none") } diff --git a/cmd/ef-tests-cl/consensus_tests/sanity.go b/cmd/ef-tests-cl/consensus_tests/sanity.go index 558fce8e8f6..9a2d54696c8 100644 --- a/cmd/ef-tests-cl/consensus_tests/sanity.go +++ b/cmd/ef-tests-cl/consensus_tests/sanity.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/transition" ) @@ -12,6 +13,7 @@ func testSanityFunction(context testContext) error { if err != nil { return err } + testState.HashSSZ() var expectedError bool expectedState, err := decodeStateFromFile(context, "post.ssz_snappy") if os.IsNotExist(err) { @@ -25,7 +27,9 @@ func testSanityFunction(context testContext) error { if err != nil { return err } - for _, block := range blocks { + startSlot := testState.Slot() + var block *cltypes.SignedBeaconBlock + for _, block = range blocks { err = transition.TransitionState(testState, block, true) if err != nil { break @@ -39,7 +43,7 @@ func testSanityFunction(context testContext) error { if expectedError { return nil } - return err + return fmt.Errorf("cannot transition state: %s. slot=%d. start_slot=%d", err, block.Block.Slot, startSlot) } expectedRoot, err := expectedState.HashSSZ() if err != nil { diff --git a/cmd/erigon-cl/core/state/accessors.go b/cmd/erigon-cl/core/state/accessors.go index 35c49a3ebbc..cd9c9d3c6a2 100644 --- a/cmd/erigon-cl/core/state/accessors.go +++ b/cmd/erigon-cl/core/state/accessors.go @@ -540,59 +540,3 @@ func (b *BeaconState) ExpectedWithdrawals() []*types.Withdrawal { // Return the withdrawals slice return withdrawals } - -// MatchingSourceAttestations retrieves pending attestations for specific epoch. -func (b *BeaconState) MatchingSourceAttestations(epoch uint64) ([]*cltypes.PendingAttestation, error) { - if b.version == clparams.Phase0Version { - panic("can call GetMatchingSourceAttestations only on Phase0") - } - if epoch == b.PreviousEpoch() { - return b.previousEpochAttestations, nil - } else if epoch == b.Epoch() { - return b.currentEpochAttestations, nil - } else { - return nil, fmt.Errorf("GetMatchingSourceAttestations: invalid epoch") - } -} - -// MatchingTargetAttestations retrieves pending attestations for specific epoch, whose vote went on the block root. -func (b *BeaconState) MatchingTargetAttestations(epoch uint64) ([]*cltypes.PendingAttestation, error) { - if b.version == clparams.Phase0Version { - panic("can call GetMatchingTargetAttestations only on Phase0") - } - sourceAttestations, err := b.MatchingSourceAttestations(epoch) - if err != nil { - return nil, err - } - ret := []*cltypes.PendingAttestation{} - blockRoot, err := b.GetBlockRoot(epoch) - if err != nil { - return nil, err - } - // Now we filter then - for _, attestation := range sourceAttestations { - if blockRoot != attestation.Data.Target.Root { - continue - } - ret = append(ret, attestation) - } - return ret, nil -} - -// UnslashedAttestingBalance total balance of attesting indicies. -func (b *BeaconState) UnslashedAttestingBalance(attestations []*cltypes.PendingAttestation) (uint64, error) { - sum := uint64(0) - for _, attestation := range attestations { - attestingIndicies, err := b.GetAttestingIndicies(attestation.Data, attestation.AggregationBits, true) - if err != nil { - return 0, err - } - for _, index := range attestingIndicies { - if b.validators[index].Slashed { - continue - } - sum += b.validators[index].EffectiveBalance - } - } - return sum, nil -} diff --git a/cmd/erigon-cl/core/state/state.go b/cmd/erigon-cl/core/state/state.go index 69b56d0d4dd..6c71e0278a8 100644 --- a/cmd/erigon-cl/core/state/state.go +++ b/cmd/erigon-cl/core/state/state.go @@ -173,9 +173,11 @@ func (b *BeaconState) _initializeValidatorsPhase0() error { b.validators[index].MinPreviousInclusionDelayAttestation = attestation } b.validators[index].IsPreviousMatchingSourceAttester = true - if attestation.Data.Target.Root == previousEpochRoot { - b.validators[index].IsPreviousMatchingTargetAttester = true + if attestation.Data.Target.Root != previousEpochRoot { + continue } + b.validators[index].IsPreviousMatchingTargetAttester = true + if attestation.Data.BeaconBlockHash == slotRoot { b.validators[index].IsPreviousMatchingHeadAttester = true } diff --git a/cmd/erigon-cl/core/transition/process_attestations.go b/cmd/erigon-cl/core/transition/process_attestations.go index 0cedf1f6f77..9ae5d6edbb0 100644 --- a/cmd/erigon-cl/core/transition/process_attestations.go +++ b/cmd/erigon-cl/core/transition/process_attestations.go @@ -150,6 +150,8 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att validator.IsCurrentMatchingSourceAttester = true if attestation.Data.Target.Root == epochRoot { validator.IsCurrentMatchingTargetAttester = true + } else { + continue } if attestation.Data.BeaconBlockHash == slotRoot { validator.IsCurrentMatchingHeadAttester = true @@ -159,9 +161,10 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att validator.MinPreviousInclusionDelayAttestation = pendingAttestation } validator.IsPreviousMatchingSourceAttester = true - if attestation.Data.Target.Root == epochRoot { - validator.IsPreviousMatchingTargetAttester = true + if attestation.Data.Target.Root != epochRoot { + continue } + validator.IsPreviousMatchingTargetAttester = true if attestation.Data.BeaconBlockHash == slotRoot { validator.IsPreviousMatchingHeadAttester = true } diff --git a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go index 81e9567fdaf..d7c50ddf8df 100644 --- a/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go +++ b/cmd/erigon-cl/core/transition/process_rewards_and_penalties.go @@ -8,9 +8,6 @@ import ( func processRewardsAndPenaltiesPostAltair(state *state.BeaconState) (err error) { beaconConfig := state.BeaconConfig() weights := beaconConfig.ParticipationWeights() - if state.Epoch() == beaconConfig.GenesisEpoch { - return nil - } eligibleValidators := state.EligibleValidatorsIndicies() // Initialize variables totalActiveBalance := state.GetTotalActiveBalance() @@ -177,6 +174,9 @@ func processRewardsAndPenaltiesPhase0(state *state.BeaconState) (err error) { // ProcessRewardsAndPenalties applies rewards/penalties accumulated during previous epoch. func ProcessRewardsAndPenalties(state *state.BeaconState) error { + if state.Epoch() == state.BeaconConfig().GenesisEpoch { + return nil + } if state.Version() == clparams.Phase0Version { return processRewardsAndPenaltiesPhase0(state) } From 9ea485ee5b0a697200fd86edd9be6db480eb0d32 Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Sat, 11 Mar 2023 18:51:22 +0100 Subject: [PATCH 7/7] fixed test & lint --- cmd/erigon-cl/core/state/ssz.go | 15 ++++----------- cmd/erigon-cl/core/state/ssz_test.go | 2 +- cmd/erigon-cl/core/transition/process_epoch.go | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/cmd/erigon-cl/core/state/ssz.go b/cmd/erigon-cl/core/state/ssz.go index 3891331fb57..726b4a2325e 100644 --- a/cmd/erigon-cl/core/state/ssz.go +++ b/cmd/erigon-cl/core/state/ssz.go @@ -237,13 +237,6 @@ func (b *BeaconState) EncodeSSZ(buf []byte) ([]byte, error) { } func (b *BeaconState) DecodeSSZWithVersion(buf []byte, version int) error { - // Initialize beacon state - defer func() { - if err := b.initBeaconState(); err != nil { - panic(err) - } - }() - b.version = clparams.StateVersion(version) if len(buf) < b.EncodingSizeSSZ() { return ssz_utils.ErrLowBufferSize @@ -385,7 +378,7 @@ func (b *BeaconState) DecodeSSZWithVersion(buf []byte, version int) error { if b.currentEpochAttestations, err = ssz_utils.DecodeDynamicList[*cltypes.PendingAttestation](buf, currentEpochParticipationOffset, uint32(len(buf)), maxAttestations); err != nil { return err } - return nil + return b.initBeaconState() } else { var previousEpochParticipation, currentEpochParticipation []byte if previousEpochParticipation, err = ssz_utils.DecodeString(buf, uint64(previousEpochParticipationOffset), uint64(currentEpochParticipationOffset), state_encoding.ValidatorRegistryLimit); err != nil { @@ -406,7 +399,7 @@ func (b *BeaconState) DecodeSSZWithVersion(buf []byte, version int) error { return err } if b.version == clparams.AltairVersion { - return nil + return b.initBeaconState() } endOffset = uint32(len(buf)) if historicalSummariesOffset != 0 { @@ -421,13 +414,13 @@ func (b *BeaconState) DecodeSSZWithVersion(buf []byte, version int) error { return err } if b.version == clparams.BellatrixVersion { - return nil + return b.initBeaconState() } if b.historicalSummaries, err = ssz_utils.DecodeStaticList[*cltypes.HistoricalSummary](buf, historicalSummariesOffset, uint32(len(buf)), 64, state_encoding.HistoricalRootsLength); err != nil { return err } // Capella - return nil + return b.initBeaconState() } // SSZ size of the Beacon State diff --git a/cmd/erigon-cl/core/state/ssz_test.go b/cmd/erigon-cl/core/state/ssz_test.go index eb2d0557027..1748ec2ca0c 100644 --- a/cmd/erigon-cl/core/state/ssz_test.go +++ b/cmd/erigon-cl/core/state/ssz_test.go @@ -36,7 +36,7 @@ func TestBeaconStatePhase0EncodingDecoding(t *testing.T) { state := state.New(&clparams.MainnetBeaconConfig) decodedSSZ, err := utils.DecompressSnappy(phase0BeaconSnappyTest) require.NoError(t, err) - require.NoError(t, state.DecodeSSZWithVersion(decodedSSZ, int(clparams.Phase0Version))) + state.DecodeSSZWithVersion(decodedSSZ, int(clparams.Phase0Version)) root, err := state.HashSSZ() require.NoError(t, err) require.Equal(t, libcommon.Hash(root), libcommon.HexToHash("0x5fd0bc1a74028b598f2eb0190a25e58896b26d4aad24fa9cb7672e6114e01446")) diff --git a/cmd/erigon-cl/core/transition/process_epoch.go b/cmd/erigon-cl/core/transition/process_epoch.go index f8e7169431d..61f61c5cf06 100644 --- a/cmd/erigon-cl/core/transition/process_epoch.go +++ b/cmd/erigon-cl/core/transition/process_epoch.go @@ -64,7 +64,7 @@ func ProcessParticipationRecordUpdates(state *state.BeaconState) error { validator.IsCurrentMatchingTargetAttester = false validator.IsCurrentMatchingHeadAttester = false // Setting the validator - if err := state.SetValidatorAt(int(validatorIndex), validator); err != nil { + if err := state.SetValidatorAt(validatorIndex, validator); err != nil { return err } }