From 611e5bbd743d172f9d3797bb806dab8f8f59c783 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Mon, 27 Mar 2023 00:25:08 +0200 Subject: [PATCH] Added reverse beacon changeset for beacon state rewind (#7185) Added changesets for beacon chain to implement memory efficient fork choice --- cl/cltypes/attestations.go | 1 - cl/cltypes/justification_bits.go | 4 + cl/cltypes/participation_flags.go | 6 + cmd/ef-tests-cl/consensus_tests/fork.go | 23 ++ cmd/ef-tests-cl/consensus_tests/sanity.go | 48 +++- .../core/beacon_changeset/list_changeset.go | 146 ++++++++++ .../beacon_changeset/list_changeset_test.go | 45 +++ .../reverse_beacon_state_changeset.go | 241 ++++++++++++++++ cmd/erigon-cl/core/state/accessors.go | 6 +- cmd/erigon-cl/core/state/accessors_test.go | 27 -- cmd/erigon-cl/core/state/changeset.go | 260 ++++++++++++++++++ cmd/erigon-cl/core/state/getters.go | 22 +- cmd/erigon-cl/core/state/mutators.go | 17 +- cmd/erigon-cl/core/state/mutators_test.go | 8 +- cmd/erigon-cl/core/state/params.go | 51 ++-- cmd/erigon-cl/core/state/setters.go | 192 +++++++++---- cmd/erigon-cl/core/state/state.go | 3 + cmd/erigon-cl/core/state/upgrade.go | 9 + .../finalization_and_justification.go | 2 +- cmd/erigon-cl/core/transition/operations.go | 8 +- .../core/transition/operations_test.go | 12 +- .../core/transition/process_attestations.go | 28 +- .../process_bls_to_execution_change.go | 13 +- .../process_effective_balance_update.go | 6 +- .../core/transition/process_epoch.go | 10 +- .../transition/process_registry_updates.go | 14 +- .../core/transition/process_slots.go | 7 +- .../core/transition/process_slots_test.go | 2 +- cmd/erigon-cl/core/transition/processing.go | 8 +- .../core/transition/processing_test.go | 8 +- cmd/erigon-cl/core/transition/resets.go | 4 +- cmd/erigon-cl/stages/stage_beacon_blocks.go | 5 +- 32 files changed, 1034 insertions(+), 202 deletions(-) create mode 100644 cmd/erigon-cl/core/beacon_changeset/list_changeset.go create mode 100644 cmd/erigon-cl/core/beacon_changeset/list_changeset_test.go create mode 100644 cmd/erigon-cl/core/beacon_changeset/reverse_beacon_state_changeset.go create mode 100644 cmd/erigon-cl/core/state/changeset.go diff --git a/cl/cltypes/attestations.go b/cl/cltypes/attestations.go index c3bd2e4bfc6..619a49c3ed7 100644 --- a/cl/cltypes/attestations.go +++ b/cl/cltypes/attestations.go @@ -531,7 +531,6 @@ func (a *PendingAttestation) EncodeSSZ(buf []byte) (dst []byte, err error) { if dst, err = a.Data.EncodeSSZ(dst); err != nil { return } - fmt.Println(dst) dst = append(dst, ssz.Uint64SSZ(a.InclusionDelay)...) dst = append(dst, ssz.Uint64SSZ(a.ProposerIndex)...) diff --git a/cl/cltypes/justification_bits.go b/cl/cltypes/justification_bits.go index 84e48931a80..c83f939ad41 100644 --- a/cl/cltypes/justification_bits.go +++ b/cl/cltypes/justification_bits.go @@ -33,3 +33,7 @@ func (j JustificationBits) CheckRange(start int, end int) bool { } return true } + +func (j JustificationBits) Copy() JustificationBits { + return JustificationBits{j[0], j[1], j[2], j[3]} +} diff --git a/cl/cltypes/participation_flags.go b/cl/cltypes/participation_flags.go index 8e5265333e3..b9dfbdfff87 100644 --- a/cl/cltypes/participation_flags.go +++ b/cl/cltypes/participation_flags.go @@ -25,6 +25,12 @@ func (p ParticipationFlagsList) Bytes() []byte { return b } +func (p ParticipationFlagsList) Copy() ParticipationFlagsList { + c := make(ParticipationFlagsList, len(p)) + copy(c, p) + return c +} + func ParticipationFlagsListFromBytes(buf []byte) ParticipationFlagsList { flagsList := make([]ParticipationFlags, len(buf)) for i := range flagsList { diff --git a/cmd/ef-tests-cl/consensus_tests/fork.go b/cmd/ef-tests-cl/consensus_tests/fork.go index 432d2d10b53..bcc2c4f639c 100644 --- a/cmd/ef-tests-cl/consensus_tests/fork.go +++ b/cmd/ef-tests-cl/consensus_tests/fork.go @@ -20,6 +20,7 @@ func forkTest(context testContext) error { return err } + preState.StartCollectingReverseChangeSet() if preState.Version() == clparams.Phase0Version { if err := preState.UpgradeToAltair(); err != nil { return err @@ -33,6 +34,8 @@ func forkTest(context testContext) error { return err } } + change := preState.StopCollectingReverseChangeSet() + if expectedError { return fmt.Errorf("expected error") } @@ -47,5 +50,25 @@ func forkTest(context testContext) error { if root != expectedRoot { return fmt.Errorf("mismatching state roots") } + if context.version == clparams.AltairVersion { + return nil + } + // now do unwind + initialState, err := decodeStateFromFile(prevContext, "pre.ssz_snappy") + if err != nil { + return err + } + preState.RevertWithChangeset(change) + root, err = preState.HashSSZ() + if err != nil { + return err + } + expectedRoot, err = initialState.HashSSZ() + if err != nil { + return err + } + if root != expectedRoot { + return fmt.Errorf("mismatching state roots with unwind") + } return nil } diff --git a/cmd/ef-tests-cl/consensus_tests/sanity.go b/cmd/ef-tests-cl/consensus_tests/sanity.go index 9a2d54696c8..0aaf9abf403 100644 --- a/cmd/ef-tests-cl/consensus_tests/sanity.go +++ b/cmd/ef-tests-cl/consensus_tests/sanity.go @@ -4,7 +4,9 @@ import ( "fmt" "os" + "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/beacon_changeset" "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/transition" ) @@ -28,12 +30,16 @@ func testSanityFunction(context testContext) error { return err } startSlot := testState.Slot() + + changes := []*beacon_changeset.ReverseBeaconStateChangeSet{} var block *cltypes.SignedBeaconBlock for _, block = range blocks { + testState.StartCollectingReverseChangeSet() err = transition.TransitionState(testState, block, true) if err != nil { break } + changes = append(changes, testState.StopCollectingReverseChangeSet()) } // Deal with transition error if expectedError && err == nil { @@ -45,7 +51,7 @@ func testSanityFunction(context testContext) error { } return fmt.Errorf("cannot transition state: %s. slot=%d. start_slot=%d", err, block.Block.Slot, startSlot) } - expectedRoot, err := expectedState.HashSSZ() + finalRoot, err := expectedState.HashSSZ() if err != nil { return err } @@ -53,9 +59,47 @@ func testSanityFunction(context testContext) error { if err != nil { return err } - if haveRoot != expectedRoot { + if haveRoot != finalRoot { return fmt.Errorf("mismatching state roots") } + if context.version == clparams.Phase0Version { + return nil + } + // Now do the unwind + initialState, err := decodeStateFromFile(context, "pre.ssz_snappy") + if err != nil { + return err + } + _ = initialState + for i := len(changes) - 1; i >= 0; i-- { + testState.RevertWithChangeset(changes[i]) + } + + expectedRoot, err := initialState.HashSSZ() + if err != nil { + return err + } + + haveRoot, err = testState.HashSSZ() + if err != nil { + return err + } + + if haveRoot != expectedRoot { + return fmt.Errorf("mismatching state roots with unwind") + } + // Execute them back (ensure cache is good.) + for _, block = range blocks { + testState.StartCollectingReverseChangeSet() + err = transition.TransitionState(testState, block, true) + if err != nil { + break + } + changes = append(changes, testState.StopCollectingReverseChangeSet()) + } + if err != nil { + return err + } return nil } diff --git a/cmd/erigon-cl/core/beacon_changeset/list_changeset.go b/cmd/erigon-cl/core/beacon_changeset/list_changeset.go new file mode 100644 index 00000000000..2cd4e6a6f48 --- /dev/null +++ b/cmd/erigon-cl/core/beacon_changeset/list_changeset.go @@ -0,0 +1,146 @@ +package beacon_changeset + +import ( + "sort" +) + +type ListChangeSet[T any] struct { + list []*listElementChangeset[T] + listLength int + nextId int + compact bool +} + +type listElementChangeset[T any] struct { + value T + listIndex int + id int +} + +// NewListChangeSet creates new list with given length. +func NewListChangeSet[T any](length int) *ListChangeSet[T] { + return &ListChangeSet[T]{listLength: length} +} + +// AddChange appens to a new change to the changeset of the list. +func (l *ListChangeSet[T]) AddChange(index int, elem T) { + l.compact = false + l.list = append(l.list, &listElementChangeset[T]{ + value: elem, + listIndex: index, + id: l.nextId, + }) + l.nextId++ +} + +// CompactChanges removes duplicates from a list using QuickSort and linear scan. +// duplicates may appear if one state parameter is changed more than once. +func (l *ListChangeSet[T]) CompactChanges() { + if l.compact { + return + } + l.compact = true + // Check if there are any duplicates to remove. + if len(l.list) < 2 { + return + } + + // Sort the list using QuickSort. + sort.Slice(l.list, func(i, j int) bool { + if l.list[i].listIndex == l.list[j].listIndex { + return l.list[i].id < l.list[j].id + } + return l.list[i].listIndex < l.list[j].listIndex + }) + + // Create a new list buffer for the compacted list. + compactList := []*listElementChangeset[T]{} + + // Do a linear scan through the sorted list and remove duplicates. + previousIndexElement := l.list[0] + for _, listElement := range l.list { + if listElement.listIndex != previousIndexElement.listIndex { + compactList = append(compactList, previousIndexElement) + } + previousIndexElement = listElement + } + compactList = append(compactList, previousIndexElement) + + // Update the original list with the compacted list. + l.list = compactList +} + +// CompactChangesReverse removes duplicates from a list using QuickSort and linear scan. +// duplicates may appear if one state parameter is changed more than once. +// Difference with CompactChanges is that the sorting is reversed. +func (l *ListChangeSet[T]) CompactChangesReverse() { + if l.compact { + return + } + l.compact = true + // Check if there are any duplicates to remove. + if len(l.list) < 2 { + return + } + + // Sort the list using QuickSort. + sort.Slice(l.list, func(i, j int) bool { + if l.list[i].listIndex == l.list[j].listIndex { + return l.list[i].id > l.list[j].id + } + return l.list[i].listIndex < l.list[j].listIndex + }) + + // Create a new list buffer for the compacted list. + compactList := []*listElementChangeset[T]{} + + // Do a linear scan through the sorted list and remove duplicates. + previousIndexElement := l.list[0] + for _, listElement := range l.list { + if listElement.listIndex != previousIndexElement.listIndex { + compactList = append(compactList, previousIndexElement) + } + previousIndexElement = listElement + } + compactList = append(compactList, previousIndexElement) + // Update the original list with the compacted list. + l.list = compactList +} + +// ApplyChanges Apply changes without any mercy. if it is reverse, you need to call CompactChangesReverse before. +func (l *ListChangeSet[T]) ApplyChanges(input []T) (output []T, changed bool) { + if len(l.list) == 0 && l.listLength == len(input) { + output = input + return + } + changed = true + // Re-adjust list size. + output = make([]T, l.listLength) + copy(output, input) + // Now apply changes to the given list + for _, elem := range l.list { + if elem.listIndex >= len(output) { + continue + } + output[elem.listIndex] = elem.value + } + return +} + +// ChangesWithHandler uses custom handler to handle changes. +func (l *ListChangeSet[T]) ChangesWithHandler(fn func(value T, index int)) { + // Now apply changes to the given list + for _, elem := range l.list { + fn(elem.value, elem.listIndex) + } +} + +// ListLength return full list length +func (l *ListChangeSet[T]) ListLength() int { + return l.listLength +} + +// Empty return whether current list diff is empty +func (l *ListChangeSet[T]) Empty() bool { + return len(l.list) == 0 +} diff --git a/cmd/erigon-cl/core/beacon_changeset/list_changeset_test.go b/cmd/erigon-cl/core/beacon_changeset/list_changeset_test.go new file mode 100644 index 00000000000..ea82883aaf8 --- /dev/null +++ b/cmd/erigon-cl/core/beacon_changeset/list_changeset_test.go @@ -0,0 +1,45 @@ +package beacon_changeset + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestListChangeset(t *testing.T) { + pre := []int{6, 8, 9} + changeset := NewListChangeSet[int](3) + changeset.AddChange(1, 45) + changeset.AddChange(1, 1) + changeset.AddChange(2, 45) + changeset.CompactChanges() + require.Equal(t, len(changeset.list), 2) + post, changed := changeset.ApplyChanges(pre) + require.Equal(t, post, []int{6, 1, 45}) + require.Equal(t, changed, true) +} + +func TestListChangesetWithReverse(t *testing.T) { + pre := []int{6, 8, 9} + changeset := NewListChangeSet[int](3) + changeset.AddChange(1, 45) + changeset.AddChange(1, 1) + changeset.AddChange(2, 45) + changeset.CompactChangesReverse() + require.Equal(t, len(changeset.list), 2) + post, changed := changeset.ApplyChanges(pre) + require.Equal(t, post, []int{6, 45, 45}) + require.Equal(t, changed, true) +} + +func TestListChangesetWithoutCompact(t *testing.T) { + pre := []int{6, 8, 9} + changeset := NewListChangeSet[int](3) + changeset.AddChange(1, 45) + changeset.AddChange(1, 1) + changeset.AddChange(2, 45) + require.Equal(t, len(changeset.list), 3) + post, changed := changeset.ApplyChanges(pre) + require.Equal(t, post, []int{6, 1, 45}) + require.Equal(t, changed, true) +} diff --git a/cmd/erigon-cl/core/beacon_changeset/reverse_beacon_state_changeset.go b/cmd/erigon-cl/core/beacon_changeset/reverse_beacon_state_changeset.go new file mode 100644 index 00000000000..3d33fcf97e0 --- /dev/null +++ b/cmd/erigon-cl/core/beacon_changeset/reverse_beacon_state_changeset.go @@ -0,0 +1,241 @@ +package beacon_changeset + +import ( + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes" +) + +// This type of changeset is the diff beetwen next state and input state and is used to reverse beacon state. +// It does not work the other way around. So they apply [curr state] + [reverse change set] = [prev state] +type ReverseBeaconStateChangeSet struct { + // Single types. + SlotChange *uint64 + ForkChange *cltypes.Fork + LatestBlockHeaderChange *cltypes.BeaconBlockHeader + Eth1DataChange *cltypes.Eth1Data + Eth1DepositIndexChange *uint64 + JustificationBitsChange *cltypes.JustificationBits + PreviousJustifiedCheckpointChange *cltypes.Checkpoint + CurrentJustifiedCheckpointChange *cltypes.Checkpoint + FinalizedCheckpointChange *cltypes.Checkpoint + CurrentSyncCommitteeChange *cltypes.SyncCommittee + NextSyncCommitteeChange *cltypes.SyncCommittee + LatestExecutionPayloadHeaderChange *cltypes.Eth1Header + NextWithdrawalIndexChange *uint64 + NextWithdrawalValidatorIndexChange *uint64 + VersionChange *clparams.StateVersion + // Lists and arrays changesets + BlockRootsChanges *ListChangeSet[libcommon.Hash] + StateRootsChanges *ListChangeSet[libcommon.Hash] + HistoricalRootsChanges *ListChangeSet[libcommon.Hash] + Eth1DataVotesChanges *ListChangeSet[cltypes.Eth1Data] + BalancesChanges *ListChangeSet[uint64] + RandaoMixesChanges *ListChangeSet[libcommon.Hash] + SlashingsChanges *ListChangeSet[uint64] + PreviousEpochParticipationChanges *ListChangeSet[cltypes.ParticipationFlags] + CurrentEpochParticipationChanges *ListChangeSet[cltypes.ParticipationFlags] + InactivityScoresChanges *ListChangeSet[uint64] + HistoricalSummaryChange *ListChangeSet[cltypes.HistoricalSummary] + // Validator fields. + WithdrawalCredentialsChange *ListChangeSet[libcommon.Hash] + EffectiveBalanceChange *ListChangeSet[uint64] + SlashedChange *ListChangeSet[bool] + ActivationEligibilityEpochChange *ListChangeSet[uint64] + ActivationEpochChange *ListChangeSet[uint64] + ExitEpochChange *ListChangeSet[uint64] + WithdrawalEpochChange *ListChangeSet[uint64] + // Efficient unwinding on reset (only applicable at epoch boundaries) + PreviousEpochParticipationAtReset cltypes.ParticipationFlagsList + CurrentEpochParticipationAtReset cltypes.ParticipationFlagsList + Eth1DataVotesAtReset []*cltypes.Eth1Data +} + +func (r *ReverseBeaconStateChangeSet) OnSlotChange(prevSlot uint64) { + if r.SlotChange != nil { + return + } + r.SlotChange = new(uint64) + *r.SlotChange = prevSlot +} + +func (r *ReverseBeaconStateChangeSet) OnForkChange(fork *cltypes.Fork) { + if r.ForkChange != nil { + return + } + r.ForkChange = new(cltypes.Fork) + *r.ForkChange = *fork +} + +func (r *ReverseBeaconStateChangeSet) OnLatestHeaderChange(h *cltypes.BeaconBlockHeader) { + if r.LatestBlockHeaderChange != nil { + return + } + r.LatestBlockHeaderChange = new(cltypes.BeaconBlockHeader) + *r.LatestBlockHeaderChange = *h +} + +func (r *ReverseBeaconStateChangeSet) OnEth1DataChange(e *cltypes.Eth1Data) { + if r.LatestBlockHeaderChange != nil { + return + } + r.Eth1DataChange = new(cltypes.Eth1Data) + *r.Eth1DataChange = *e +} + +func (r *ReverseBeaconStateChangeSet) OnJustificationBitsChange(j cltypes.JustificationBits) { + if r.JustificationBitsChange != nil { + return + } + r.JustificationBitsChange = new(cltypes.JustificationBits) + *r.JustificationBitsChange = j.Copy() +} + +func (r *ReverseBeaconStateChangeSet) OnEth1DepositIndexChange(e uint64) { + if r.Eth1DepositIndexChange != nil { + return + } + r.Eth1DepositIndexChange = new(uint64) + *r.Eth1DepositIndexChange = e +} + +func (r *ReverseBeaconStateChangeSet) OnPreviousJustifiedCheckpointChange(c *cltypes.Checkpoint) { + if r.PreviousJustifiedCheckpointChange != nil { + return + } + r.PreviousJustifiedCheckpointChange = new(cltypes.Checkpoint) + *r.PreviousJustifiedCheckpointChange = *c +} + +func (r *ReverseBeaconStateChangeSet) OnCurrentJustifiedCheckpointChange(c *cltypes.Checkpoint) { + if r.CurrentJustifiedCheckpointChange != nil { + return + } + r.CurrentJustifiedCheckpointChange = new(cltypes.Checkpoint) + *r.CurrentJustifiedCheckpointChange = *c +} + +func (r *ReverseBeaconStateChangeSet) OnFinalizedCheckpointChange(c *cltypes.Checkpoint) { + if r.FinalizedCheckpointChange != nil { + return + } + r.FinalizedCheckpointChange = new(cltypes.Checkpoint) + *r.FinalizedCheckpointChange = *c +} + +func (r *ReverseBeaconStateChangeSet) OnCurrentSyncCommitteeChange(c *cltypes.SyncCommittee) { + if r.CurrentSyncCommitteeChange != nil { + return + } + r.CurrentSyncCommitteeChange = new(cltypes.SyncCommittee) + *r.CurrentSyncCommitteeChange = *c +} + +func (r *ReverseBeaconStateChangeSet) OnNextSyncCommitteeChange(c *cltypes.SyncCommittee) { + if r.NextSyncCommitteeChange != nil { + return + } + r.NextSyncCommitteeChange = new(cltypes.SyncCommittee) + *r.NextSyncCommitteeChange = *c +} + +func (r *ReverseBeaconStateChangeSet) OnEth1Header(e *cltypes.Eth1Header) { + if r.LatestExecutionPayloadHeaderChange != nil { + return + } + r.LatestExecutionPayloadHeaderChange = new(cltypes.Eth1Header) + *r.LatestExecutionPayloadHeaderChange = *e +} + +func (r *ReverseBeaconStateChangeSet) OnNextWithdrawalIndexChange(index uint64) { + if r.NextWithdrawalIndexChange != nil { + return + } + r.NextWithdrawalIndexChange = new(uint64) + *r.NextWithdrawalIndexChange = index +} + +func (r *ReverseBeaconStateChangeSet) OnNextWithdrawalValidatorIndexChange(index uint64) { + if r.NextWithdrawalValidatorIndexChange != nil { + return + } + r.NextWithdrawalValidatorIndexChange = new(uint64) + *r.NextWithdrawalValidatorIndexChange = index +} + +func (r *ReverseBeaconStateChangeSet) OnVersionChange(v clparams.StateVersion) { + if r.VersionChange != nil { + return + } + r.VersionChange = new(clparams.StateVersion) + *r.VersionChange = v +} + +func (r *ReverseBeaconStateChangeSet) HasValidatorSetNotChanged(validatorSetLength int) bool { + return validatorSetLength == r.WithdrawalCredentialsChange.ListLength() && r.WithdrawalCredentialsChange.Empty() && r.ActivationEligibilityEpochChange.Empty() && r.ActivationEpochChange.Empty() && + r.EffectiveBalanceChange.Empty() && r.SlashedChange.Empty() && r.ExitEpochChange.Empty() && r.WithdrawalEpochChange.Empty() +} + +func (r *ReverseBeaconStateChangeSet) ApplyEth1DataVotesChanges(input []*cltypes.Eth1Data) (output []*cltypes.Eth1Data, changed bool) { + output = input + if r.Eth1DataVotesChanges.Empty() && r.Eth1DataVotesChanges.ListLength() == len(output) { + return + } + changed = true + if r.Eth1DataVotesChanges.ListLength() != len(output) { + output = make([]*cltypes.Eth1Data, r.Eth1DataVotesChanges.ListLength()) + copy(output, input) + } + r.Eth1DataVotesChanges.ChangesWithHandler(func(value cltypes.Eth1Data, index int) { + *output[index] = value + }) + return +} + +func (r *ReverseBeaconStateChangeSet) ApplyHistoricalSummaryChanges(input []*cltypes.HistoricalSummary) (output []*cltypes.HistoricalSummary, changed bool) { + output = input + if r.HistoricalSummaryChange.Empty() && r.Eth1DataVotesChanges.ListLength() == len(output) { + return + } + changed = true + historicalSummarryLength := r.HistoricalSummaryChange.ListLength() + if historicalSummarryLength != len(output) { + output = make([]*cltypes.HistoricalSummary, historicalSummarryLength) + copy(output, input) + } + r.HistoricalSummaryChange.ChangesWithHandler(func(value cltypes.HistoricalSummary, index int) { + *output[index] = value + }) + return +} + +func (r *ReverseBeaconStateChangeSet) CompactChanges() { + + r.BlockRootsChanges.CompactChangesReverse() + r.StateRootsChanges.CompactChangesReverse() + r.HistoricalRootsChanges.CompactChangesReverse() + r.SlashingsChanges.CompactChangesReverse() + r.RandaoMixesChanges.CompactChangesReverse() + r.BalancesChanges.CompactChangesReverse() + if len(r.Eth1DataVotesAtReset) > 0 { + r.Eth1DataVotesChanges = nil + } else { + r.Eth1DataVotesChanges.CompactChangesReverse() + } + if len(r.PreviousEpochParticipationAtReset) > 0 { + r.PreviousEpochParticipationChanges = nil + r.CurrentEpochParticipationChanges = nil + } else { + r.PreviousEpochParticipationChanges.CompactChangesReverse() + r.CurrentEpochParticipationChanges.CompactChangesReverse() + } + r.InactivityScoresChanges.CompactChangesReverse() + r.HistoricalRootsChanges.CompactChangesReverse() + r.WithdrawalCredentialsChange.CompactChangesReverse() + r.EffectiveBalanceChange.CompactChangesReverse() + r.ExitEpochChange.CompactChangesReverse() + r.ActivationEligibilityEpochChange.CompactChangesReverse() + r.ActivationEpochChange.CompactChangesReverse() + r.SlashedChange.CompactChangesReverse() + r.WithdrawalEpochChange.CompactChangesReverse() +} diff --git a/cmd/erigon-cl/core/state/accessors.go b/cmd/erigon-cl/core/state/accessors.go index 9859425e8a7..0e02132b6ba 100644 --- a/cmd/erigon-cl/core/state/accessors.go +++ b/cmd/erigon-cl/core/state/accessors.go @@ -70,7 +70,7 @@ func (b *BeaconState) GetUnslashedParticipatingIndices(flagIndex int, epoch uint return nil, fmt.Errorf("getUnslashedParticipatingIndices: only epoch and previous epoch can be used") } // Iterate over all validators and include the active ones that have flag_index enabled and are not slashed. - for i, validator := range b.Validators() { + for i, validator := range b.validators { if !validator.Active(epoch) || !participation[i].HasFlag(flagIndex) || validator.Slashed { @@ -228,7 +228,7 @@ func (b *BeaconState) ComputeProposerIndex(indices []uint64, seed [32]byte) (uin input := append(seed[:], buf...) randomByte := uint64(utils.Keccak256(input)[i%32]) - validator, err := b.ValidatorAt(int(candidateIndex)) + validator, err := b.ValidatorForValidatorIndex(int(candidateIndex)) if err != nil { return 0, err } @@ -565,7 +565,7 @@ func (b *BeaconState) ComputeNextSyncCommittee() (*cltypes.SyncCommittee, error) input := append(seed[:], buf...) randomByte := uint64(utils.Keccak256(input)[i%32]) // retrieve validator. - validator, err := b.ValidatorAt(int(candidateIndex)) + validator, err := b.ValidatorForValidatorIndex(int(candidateIndex)) if err != nil { return nil, err } diff --git a/cmd/erigon-cl/core/state/accessors_test.go b/cmd/erigon-cl/core/state/accessors_test.go index 5e34d5be018..08fc9f40829 100644 --- a/cmd/erigon-cl/core/state/accessors_test.go +++ b/cmd/erigon-cl/core/state/accessors_test.go @@ -33,33 +33,6 @@ func getTestState(t *testing.T) *state.BeaconState { return b } -func TestActiveValidatorIndices(t *testing.T) { - epoch := uint64(2) - testState := state.GetEmptyBeaconState() - // Not Active validator - testState.AddValidator(&cltypes.Validator{ - ActivationEpoch: 3, - ExitEpoch: 9, - EffectiveBalance: 2e9, - }, 2e9) - // Active Validator - testState.AddValidator(&cltypes.Validator{ - ActivationEpoch: 1, - ExitEpoch: 9, - EffectiveBalance: 2e9, - }, 2e9) - testState.SetSlot(epoch * 32) // Epoch - testFlags := cltypes.ParticipationFlagsListFromBytes([]byte{1, 1}) - testState.SetCurrentEpochParticipation(testFlags) - // Only validator at index 1 (second validator) is active. - require.Equal(t, testState.GetActiveValidatorsIndices(epoch), []uint64{1}) - set, err := testState.GetUnslashedParticipatingIndices(0x00, epoch) - require.NoError(t, err) - require.Equal(t, set, []uint64{1}) - // Check if balances are retrieved correctly - require.Equal(t, testState.GetTotalActiveBalance(), uint64(2e9)) -} - func TestGetBlockRoot(t *testing.T) { epoch := uint64(2) testState := state.GetEmptyBeaconState() diff --git a/cmd/erigon-cl/core/state/changeset.go b/cmd/erigon-cl/core/state/changeset.go new file mode 100644 index 00000000000..36b489895da --- /dev/null +++ b/cmd/erigon-cl/core/state/changeset.go @@ -0,0 +1,260 @@ +package state + +import ( + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/beacon_changeset" +) + +// StartCollectingReverseChangeSet starts collection change sets. +func (b *BeaconState) StartCollectingReverseChangeSet() { + b.reverseChangeset = &beacon_changeset.ReverseBeaconStateChangeSet{ + BlockRootsChanges: beacon_changeset.NewListChangeSet[libcommon.Hash](len(b.blockRoots)), + StateRootsChanges: beacon_changeset.NewListChangeSet[libcommon.Hash](len(b.stateRoots)), + HistoricalRootsChanges: beacon_changeset.NewListChangeSet[libcommon.Hash](len(b.historicalRoots)), + Eth1DataVotesChanges: beacon_changeset.NewListChangeSet[cltypes.Eth1Data](len(b.eth1DataVotes)), + BalancesChanges: beacon_changeset.NewListChangeSet[uint64](len(b.balances)), + RandaoMixesChanges: beacon_changeset.NewListChangeSet[libcommon.Hash](len(b.randaoMixes)), + SlashingsChanges: beacon_changeset.NewListChangeSet[uint64](len(b.slashings)), + PreviousEpochParticipationChanges: beacon_changeset.NewListChangeSet[cltypes.ParticipationFlags](len(b.previousEpochParticipation)), + CurrentEpochParticipationChanges: beacon_changeset.NewListChangeSet[cltypes.ParticipationFlags](len(b.currentEpochParticipation)), + InactivityScoresChanges: beacon_changeset.NewListChangeSet[uint64](len(b.inactivityScores)), + HistoricalSummaryChange: beacon_changeset.NewListChangeSet[cltypes.HistoricalSummary](len(b.historicalSummaries)), + // Validators section + WithdrawalCredentialsChange: beacon_changeset.NewListChangeSet[libcommon.Hash](len(b.validators)), + EffectiveBalanceChange: beacon_changeset.NewListChangeSet[uint64](len(b.validators)), + ActivationEligibilityEpochChange: beacon_changeset.NewListChangeSet[uint64](len(b.validators)), + ActivationEpochChange: beacon_changeset.NewListChangeSet[uint64](len(b.validators)), + ExitEpochChange: beacon_changeset.NewListChangeSet[uint64](len(b.validators)), + WithdrawalEpochChange: beacon_changeset.NewListChangeSet[uint64](len(b.validators)), + SlashedChange: beacon_changeset.NewListChangeSet[bool](len(b.validators)), + } +} + +// StopCollectingReverseChangeSet stops collection change sets. +func (b *BeaconState) StopCollectingReverseChangeSet() *beacon_changeset.ReverseBeaconStateChangeSet { + ret := b.reverseChangeset + b.reverseChangeset = nil + return ret +} + +func (b *BeaconState) RevertWithChangeset(changeset *beacon_changeset.ReverseBeaconStateChangeSet) { + changeset.CompactChanges() + beforeSlot := b.slot + var touched bool + // Updates all single types accordingly. + if changeset.SlotChange != nil { + b.slot = *changeset.SlotChange + b.touchedLeaves[SlotLeafIndex] = true + } + if changeset.ForkChange != nil { + b.fork = changeset.ForkChange + b.touchedLeaves[ForkLeafIndex] = true + } + if changeset.LatestBlockHeaderChange != nil { + b.latestBlockHeader = changeset.LatestBlockHeaderChange + b.touchedLeaves[LatestBlockHeaderLeafIndex] = true + } + if changeset.Eth1DataChange != nil { + b.eth1Data = changeset.Eth1DataChange + b.touchedLeaves[Eth1DataLeafIndex] = true + } + if changeset.Eth1DepositIndexChange != nil { + b.eth1DepositIndex = *changeset.Eth1DepositIndexChange + b.touchedLeaves[Eth1DepositIndexLeafIndex] = true + } + if changeset.JustificationBitsChange != nil { + b.justificationBits = *changeset.JustificationBitsChange + b.touchedLeaves[JustificationBitsLeafIndex] = true + } + if changeset.PreviousJustifiedCheckpointChange != nil { + b.previousJustifiedCheckpoint = changeset.PreviousJustifiedCheckpointChange + b.touchedLeaves[PreviousJustifiedCheckpointLeafIndex] = true + } + if changeset.CurrentJustifiedCheckpointChange != nil { + b.currentJustifiedCheckpoint = changeset.CurrentJustifiedCheckpointChange + b.touchedLeaves[CurrentJustifiedCheckpointLeafIndex] = true + } + if changeset.FinalizedCheckpointChange != nil { + b.finalizedCheckpoint = changeset.FinalizedCheckpointChange + b.touchedLeaves[FinalizedCheckpointLeafIndex] = true + } + if changeset.CurrentSyncCommitteeChange != nil { + b.currentSyncCommittee = changeset.CurrentSyncCommitteeChange + b.touchedLeaves[CurrentSyncCommitteeLeafIndex] = true + } + if changeset.NextSyncCommitteeChange != nil { + b.nextSyncCommittee = changeset.NextSyncCommitteeChange + b.touchedLeaves[NextSyncCommitteeLeafIndex] = true + } + if changeset.LatestExecutionPayloadHeaderChange != nil { + b.latestExecutionPayloadHeader = changeset.LatestExecutionPayloadHeaderChange + b.touchedLeaves[LatestExecutionPayloadHeaderLeafIndex] = true + } + if changeset.NextWithdrawalIndexChange != nil { + b.nextWithdrawalIndex = *changeset.NextWithdrawalIndexChange + b.touchedLeaves[NextWithdrawalIndexLeafIndex] = true + } + if changeset.NextWithdrawalValidatorIndexChange != nil { + b.nextWithdrawalValidatorIndex = *changeset.NextWithdrawalValidatorIndexChange + b.touchedLeaves[NextWithdrawalValidatorIndexLeafIndex] = true + } + if changeset.VersionChange != nil { + b.version = *changeset.VersionChange + if b.version == clparams.AltairVersion { + b.leaves[LatestExecutionPayloadHeaderLeafIndex] = libcommon.Hash{} + b.touchedLeaves[LatestExecutionPayloadHeaderLeafIndex] = false + } + if b.version == clparams.BellatrixVersion { + b.leaves[NextWithdrawalIndexLeafIndex] = libcommon.Hash{} + b.leaves[NextWithdrawalValidatorIndexLeafIndex] = libcommon.Hash{} + b.leaves[HistoricalSummariesLeafIndex] = libcommon.Hash{} + b.touchedLeaves[NextWithdrawalIndexLeafIndex] = false + b.touchedLeaves[NextWithdrawalValidatorIndexLeafIndex] = false + b.touchedLeaves[HistoricalSummariesLeafIndex] = false + } + } + // Process all the lists now. + // Start with arrays first + changeset.BlockRootsChanges.ChangesWithHandler(func(value libcommon.Hash, index int) { + b.blockRoots[index] = value + b.touchedLeaves[BlockRootsLeafIndex] = true + }) + changeset.StateRootsChanges.ChangesWithHandler(func(value libcommon.Hash, index int) { + b.stateRoots[index] = value + b.touchedLeaves[StateRootsLeafIndex] = true + }) + changeset.RandaoMixesChanges.ChangesWithHandler(func(value libcommon.Hash, index int) { + b.randaoMixes[index] = value + b.touchedLeaves[RandaoMixesLeafIndex] = true + }) + changeset.SlashingsChanges.ChangesWithHandler(func(value uint64, index int) { + b.slashings[index] = value + b.touchedLeaves[SlashingsLeafIndex] = true + }) + // Process the lists now. + b.historicalRoots, touched = changeset.HistoricalRootsChanges.ApplyChanges(b.historicalRoots) + if touched { + b.touchedLeaves[HistoricalRootsLeafIndex] = true + } + // This is a special case, as reset will lead to complete change of votes + if len(changeset.Eth1DataVotesAtReset) == 0 { + b.eth1DataVotes, touched = changeset.ApplyEth1DataVotesChanges(b.eth1DataVotes) + if touched { + b.touchedLeaves[Eth1DataVotesLeafIndex] = true + } + } else { + b.eth1DataVotes = changeset.Eth1DataVotesAtReset + b.touchedLeaves[Eth1DataVotesLeafIndex] = true + } + b.balances, touched = changeset.BalancesChanges.ApplyChanges(b.balances) + if touched { + b.touchedLeaves[BalancesLeafIndex] = true + } + // This also a special case, as this is another victim of reset, we use rotation with curr and prev to handle it efficiently + if len(changeset.PreviousEpochParticipationAtReset) == 0 && len(changeset.CurrentEpochParticipationAtReset) == 0 { + b.previousEpochParticipation, touched = changeset.PreviousEpochParticipationChanges.ApplyChanges(b.previousEpochParticipation) + if touched { + b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true + } + b.currentEpochParticipation, touched = changeset.CurrentEpochParticipationChanges.ApplyChanges(b.currentEpochParticipation) + if touched { + b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true + } + } else { + b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true + b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true + b.previousEpochParticipation = changeset.PreviousEpochParticipationAtReset.Copy() + b.currentEpochParticipation = changeset.CurrentEpochParticipationAtReset.Copy() + } + + b.inactivityScores, touched = changeset.InactivityScoresChanges.ApplyChanges(b.inactivityScores) + if touched { + b.touchedLeaves[InactivityScoresLeafIndex] = true + } + b.historicalSummaries, touched = changeset.ApplyHistoricalSummaryChanges(b.historicalSummaries) + if touched { + b.touchedLeaves[HistoricalSummariesLeafIndex] = true + } + // Now start processing validators if there are any. + if changeset.HasValidatorSetNotChanged(len(b.validators)) { + b.revertCachesOnBoundary(beforeSlot) + return + } + // We do it like this because validators diff can get quite big so we only save individual fields. + b.touchedLeaves[ValidatorsLeafIndex] = true + newValidatorsSet := b.validators + // All changes habe same length + previousValidatorSetLength := changeset.WithdrawalCredentialsChange.ListLength() + + if previousValidatorSetLength != len(b.validators) { + for _, removedValidator := range b.validators[previousValidatorSetLength:] { + delete(b.publicKeyIndicies, removedValidator.PublicKey) + } + newValidatorsSet = make([]*cltypes.Validator, previousValidatorSetLength) + copy(newValidatorsSet, b.validators) + } + b.validators = newValidatorsSet + // Now start processing all of the validator fields + changeset.WithdrawalCredentialsChange.ChangesWithHandler(func(value libcommon.Hash, index int) { + if index >= len(b.validators) { + return + } + b.validators[index].WithdrawalCredentials = value + }) + changeset.ExitEpochChange.ChangesWithHandler(func(value uint64, index int) { + if index >= len(b.validators) { + return + } + b.validators[index].ExitEpoch = value + }) + changeset.ActivationEligibilityEpochChange.ChangesWithHandler(func(value uint64, index int) { + if index >= len(b.validators) { + return + } + b.validators[index].ActivationEligibilityEpoch = value + }) + changeset.ActivationEpochChange.ChangesWithHandler(func(value uint64, index int) { + if index >= len(b.validators) { + return + } + b.validators[index].ActivationEpoch = value + }) + changeset.SlashedChange.ChangesWithHandler(func(value bool, index int) { + if index >= len(b.validators) { + return + } + b.validators[index].Slashed = value + }) + changeset.WithdrawalEpochChange.ChangesWithHandler(func(value uint64, index int) { + if index >= len(b.validators) { + return + } + b.validators[index].WithdrawableEpoch = value + }) + changeset.EffectiveBalanceChange.ChangesWithHandler(func(value uint64, index int) { + if index >= len(b.validators) { + return + } + b.validators[index].EffectiveBalance = value + }) + b.revertCachesOnBoundary(beforeSlot) +} + +func (b *BeaconState) revertCachesOnBoundary(beforeSlot uint64) { + b.activeValidatorsCache.Purge() + beforeEpoch := beforeSlot / b.beaconConfig.SlotsPerEpoch + epoch := b.Epoch() + b.committeeCache.Purge() + b.previousStateRoot = libcommon.Hash{} + b.proposerIndex = nil + b.totalActiveBalanceCache = nil + if epoch <= beforeEpoch { + return + } + for epochToBeRemoved := beforeEpoch; epochToBeRemoved < epoch+1; beforeEpoch++ { + b.shuffledSetsCache.Remove(b.GetSeed(epochToBeRemoved, b.beaconConfig.DomainBeaconAttester)) + b.activeValidatorsCache.Remove(epochToBeRemoved) + } +} diff --git a/cmd/erigon-cl/core/state/getters.go b/cmd/erigon-cl/core/state/getters.go index d23225c7803..fa889026dc8 100644 --- a/cmd/erigon-cl/core/state/getters.go +++ b/cmd/erigon-cl/core/state/getters.go @@ -39,8 +39,8 @@ func (b *BeaconState) Fork() *cltypes.Fork { return b.fork } -func (b *BeaconState) LatestBlockHeader() *cltypes.BeaconBlockHeader { - return b.latestBlockHeader +func (b *BeaconState) LatestBlockHeader() cltypes.BeaconBlockHeader { + return *b.latestBlockHeader } func (b *BeaconState) BlockRoots() [blockRootsLength]libcommon.Hash { @@ -71,7 +71,7 @@ func (b *BeaconState) Validators() []*cltypes.Validator { return b.validators } -func (b *BeaconState) ValidatorAt(index int) (*cltypes.Validator, error) { +func (b *BeaconState) ValidatorForValidatorIndex(index int) (*cltypes.Validator, error) { if index >= len(b.validators) { return nil, ErrInvalidValidatorIndex } @@ -101,18 +101,24 @@ func (b *BeaconState) SlashingSegmentAt(pos int) uint64 { return b.slashings[pos] } -func (b *BeaconState) PreviousEpochParticipation() cltypes.ParticipationFlagsList { +func (b *BeaconState) EpochParticipation(currentEpoch bool) cltypes.ParticipationFlagsList { + if currentEpoch { + return b.currentEpochParticipation + } return b.previousEpochParticipation } -func (b *BeaconState) CurrentEpochParticipation() cltypes.ParticipationFlagsList { - return b.currentEpochParticipation -} - func (b *BeaconState) JustificationBits() cltypes.JustificationBits { return b.justificationBits } +func (b *BeaconState) EpochParticipationForValidatorIndex(isCurrentEpoch bool, index int) cltypes.ParticipationFlags { + if isCurrentEpoch { + return b.currentEpochParticipation[index] + } + return b.previousEpochParticipation[index] +} + func (b *BeaconState) PreviousJustifiedCheckpoint() *cltypes.Checkpoint { return b.previousJustifiedCheckpoint } diff --git a/cmd/erigon-cl/core/state/mutators.go b/cmd/erigon-cl/core/state/mutators.go index bf0a933cc37..eb915896a02 100644 --- a/cmd/erigon-cl/core/state/mutators.go +++ b/cmd/erigon-cl/core/state/mutators.go @@ -60,12 +60,13 @@ func (b *BeaconState) InitiateValidatorExit(index uint64) error { exitQueueEpoch += 1 } - b.validators[index].ExitEpoch = exitQueueEpoch var overflow bool - if b.validators[index].WithdrawableEpoch, overflow = math.SafeAdd(b.validators[index].ExitEpoch, b.beaconConfig.MinValidatorWithdrawabilityDelay); overflow { + var newWithdrawableEpoch uint64 + if newWithdrawableEpoch, overflow = math.SafeAdd(exitQueueEpoch, b.beaconConfig.MinValidatorWithdrawabilityDelay); overflow { return fmt.Errorf("withdrawable epoch is too big") } - b.touchedLeaves[ValidatorsLeafIndex] = true + b.SetExitEpochForValidatorAtIndex(int(index), exitQueueEpoch) + b.SetWithdrawableEpochForValidatorAtIndex(int(index), newWithdrawableEpoch) return nil } @@ -81,12 +82,20 @@ func (b *BeaconState) SlashValidator(slashedInd uint64, whistleblowerInd *uint64 if err := b.InitiateValidatorExit(slashedInd); err != nil { return err } + // Record changes in changeset + slashingsIndex := int(epoch % b.beaconConfig.EpochsPerSlashingsVector) + if b.reverseChangeset != nil { + b.reverseChangeset.SlashedChange.AddChange(int(slashedInd), b.validators[slashedInd].Slashed) + b.reverseChangeset.WithdrawalEpochChange.AddChange(int(slashedInd), b.validators[slashedInd].WithdrawableEpoch) + b.reverseChangeset.SlashingsChanges.AddChange(slashingsIndex, b.slashings[slashingsIndex]) + } + // Change the validator to be slashed b.validators[slashedInd].Slashed = true b.validators[slashedInd].WithdrawableEpoch = utils.Max64(b.validators[slashedInd].WithdrawableEpoch, epoch+b.beaconConfig.EpochsPerSlashingsVector) b.touchedLeaves[ValidatorsLeafIndex] = true // Update slashings vector - b.slashings[epoch%b.beaconConfig.EpochsPerSlashingsVector] += b.validators[slashedInd].EffectiveBalance + b.slashings[slashingsIndex] += b.validators[slashedInd].EffectiveBalance b.touchedLeaves[SlashingsLeafIndex] = true if err := b.DecreaseBalance(slashedInd, b.validators[slashedInd].EffectiveBalance/b.beaconConfig.GetMinSlashingPenaltyQuotient(b.version)); err != nil { return err diff --git a/cmd/erigon-cl/core/state/mutators_test.go b/cmd/erigon-cl/core/state/mutators_test.go index 8e2c5dbe1c3..013a291568a 100644 --- a/cmd/erigon-cl/core/state/mutators_test.go +++ b/cmd/erigon-cl/core/state/mutators_test.go @@ -119,7 +119,7 @@ func TestInitiatieValidatorExit(t *testing.T) { state.SetValidators(append(state.Validators(), tc.validator)) testInd := uint64(len(state.Validators()) - 1) state.InitiateValidatorExit(testInd) - val, err := state.ValidatorAt(int(testInd)) + val, err := state.ValidatorForValidatorIndex(int(testInd)) require.NoError(t, err) if val.ExitEpoch != tc.expectedExitEpoch { t.Errorf("unexpected exit epoch: got %d, want %d", val.ExitEpoch, tc.expectedExitEpoch) @@ -146,9 +146,9 @@ func TestSlashValidator(t *testing.T) { // Set up slashed balance. preSlashBalance := uint64(1 << 20) successState.Balances()[slashedInd] = preSlashBalance - vali, err := successState.ValidatorAt(slashedInd) + vali, err := successState.ValidatorForValidatorIndex(slashedInd) require.NoError(t, err) - successState.SetValidatorAt(slashedInd, vali) + successState.SetValidatorAtIndex(slashedInd, vali) vali.EffectiveBalance = preSlashBalance testCases := []struct { @@ -177,7 +177,7 @@ func TestSlashValidator(t *testing.T) { if err != nil { t.Errorf("unexpected error, wanted success: %v", err) } - vali, err := tc.state.ValidatorAt(slashedInd) + vali, err := tc.state.ValidatorForValidatorIndex(slashedInd) require.NoError(t, err) // Check that the validator is slashed. if !vali.Slashed { diff --git a/cmd/erigon-cl/core/state/params.go b/cmd/erigon-cl/core/state/params.go index 05dae884ba0..713b6b16a0f 100644 --- a/cmd/erigon-cl/core/state/params.go +++ b/cmd/erigon-cl/core/state/params.go @@ -4,31 +4,34 @@ type StateLeafIndex uint // All position of all the leaves of the state merkle tree. const ( - GenesisTimeLeafIndex StateLeafIndex = 0 - GenesisValidatorsRootLeafIndex StateLeafIndex = 1 - SlotLeafIndex StateLeafIndex = 2 - ForkLeafIndex StateLeafIndex = 3 - LatestBlockHeaderLeafIndex StateLeafIndex = 4 - BlockRootsLeafIndex StateLeafIndex = 5 - StateRootsLeafIndex StateLeafIndex = 6 - HistoricalRootsLeafIndex StateLeafIndex = 7 - Eth1DataLeafIndex StateLeafIndex = 8 - Eth1DataVotesLeafIndex StateLeafIndex = 9 - Eth1DepositIndexLeafIndex StateLeafIndex = 10 - ValidatorsLeafIndex StateLeafIndex = 11 - BalancesLeafIndex StateLeafIndex = 12 - RandaoMixesLeafIndex StateLeafIndex = 13 - SlashingsLeafIndex StateLeafIndex = 14 - PreviousEpochParticipationLeafIndex StateLeafIndex = 15 - CurrentEpochParticipationLeafIndex StateLeafIndex = 16 - JustificationBitsLeafIndex StateLeafIndex = 17 - PreviousJustifiedCheckpointLeafIndex StateLeafIndex = 18 - CurrentJustifiedCheckpointLeafIndex StateLeafIndex = 19 - FinalizedCheckpointLeafIndex StateLeafIndex = 20 - InactivityScoresLeafIndex StateLeafIndex = 21 - CurrentSyncCommitteeLeafIndex StateLeafIndex = 22 - NextSyncCommitteeLeafIndex StateLeafIndex = 23 + GenesisTimeLeafIndex StateLeafIndex = 0 + GenesisValidatorsRootLeafIndex StateLeafIndex = 1 + SlotLeafIndex StateLeafIndex = 2 + ForkLeafIndex StateLeafIndex = 3 + LatestBlockHeaderLeafIndex StateLeafIndex = 4 + BlockRootsLeafIndex StateLeafIndex = 5 + StateRootsLeafIndex StateLeafIndex = 6 + HistoricalRootsLeafIndex StateLeafIndex = 7 + Eth1DataLeafIndex StateLeafIndex = 8 + Eth1DataVotesLeafIndex StateLeafIndex = 9 + Eth1DepositIndexLeafIndex StateLeafIndex = 10 + ValidatorsLeafIndex StateLeafIndex = 11 + BalancesLeafIndex StateLeafIndex = 12 + RandaoMixesLeafIndex StateLeafIndex = 13 + SlashingsLeafIndex StateLeafIndex = 14 + PreviousEpochParticipationLeafIndex StateLeafIndex = 15 + CurrentEpochParticipationLeafIndex StateLeafIndex = 16 + JustificationBitsLeafIndex StateLeafIndex = 17 + PreviousJustifiedCheckpointLeafIndex StateLeafIndex = 18 + CurrentJustifiedCheckpointLeafIndex StateLeafIndex = 19 + FinalizedCheckpointLeafIndex StateLeafIndex = 20 + // Altair + InactivityScoresLeafIndex StateLeafIndex = 21 + CurrentSyncCommitteeLeafIndex StateLeafIndex = 22 + NextSyncCommitteeLeafIndex StateLeafIndex = 23 + // Bellatrix LatestExecutionPayloadHeaderLeafIndex StateLeafIndex = 24 + // Capella NextWithdrawalIndexLeafIndex StateLeafIndex = 25 NextWithdrawalValidatorIndexLeafIndex StateLeafIndex = 26 HistoricalSummariesLeafIndex StateLeafIndex = 27 diff --git a/cmd/erigon-cl/core/state/setters.go b/cmd/erigon-cl/core/state/setters.go index 6f6c52fa3a1..b9d3386c3f8 100644 --- a/cmd/erigon-cl/core/state/setters.go +++ b/cmd/erigon-cl/core/state/setters.go @@ -3,7 +3,6 @@ package state import ( libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes" ) @@ -11,17 +10,10 @@ const maxEth1Votes = 2048 // Below are setters. Note that they also dirty the state. -func (b *BeaconState) SetGenesisTime(genesisTime uint64) { - b.touchedLeaves[GenesisTimeLeafIndex] = true - b.genesisTime = genesisTime -} - -func (b *BeaconState) SetGenesisValidatorsRoot(genesisValidatorRoot libcommon.Hash) { - b.touchedLeaves[GenesisValidatorsRootLeafIndex] = true - b.genesisValidatorsRoot = genesisValidatorRoot -} - func (b *BeaconState) SetSlot(slot uint64) { + if b.reverseChangeset != nil { + b.reverseChangeset.OnSlotChange(b.slot) + } b.touchedLeaves[SlotLeafIndex] = true b.slot = slot b.proposerIndex = nil @@ -32,47 +24,97 @@ func (b *BeaconState) SetSlot(slot uint64) { func (b *BeaconState) SetFork(fork *cltypes.Fork) { b.touchedLeaves[ForkLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnForkChange(b.fork) + } b.fork = fork } func (b *BeaconState) SetLatestBlockHeader(header *cltypes.BeaconBlockHeader) { b.touchedLeaves[LatestBlockHeaderLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnLatestHeaderChange(b.latestBlockHeader) + } b.latestBlockHeader = header } -func (b *BeaconState) SetHistoricalRoots(historicalRoots []libcommon.Hash) { - b.touchedLeaves[HistoricalRootsLeafIndex] = true - b.historicalRoots = historicalRoots -} - func (b *BeaconState) SetBlockRootAt(index int, root libcommon.Hash) { b.touchedLeaves[BlockRootsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.BlockRootsChanges.AddChange(index, b.blockRoots[index]) + } b.blockRoots[index] = root } func (b *BeaconState) SetStateRootAt(index int, root libcommon.Hash) { b.touchedLeaves[StateRootsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.StateRootsChanges.AddChange(index, b.stateRoots[index]) + } b.stateRoots[index] = root } func (b *BeaconState) SetHistoricalRootAt(index int, root [32]byte) { b.touchedLeaves[HistoricalRootsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.HistoricalRootsChanges.AddChange(index, b.historicalRoots[index]) + } b.historicalRoots[index] = root } -func (b *BeaconState) SetValidatorAt(index int, validator *cltypes.Validator) error { - if index >= len(b.validators) { - return ErrInvalidValidatorIndex +func (b *BeaconState) SetWithdrawalCredentialForValidatorAtIndex(index int, creds libcommon.Hash) { + b.touchedLeaves[ValidatorsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.WithdrawalCredentialsChange.AddChange(index, b.validators[index].WithdrawalCredentials) } - b.validators[index] = validator + b.validators[index].WithdrawalCredentials = creds +} + +func (b *BeaconState) SetExitEpochForValidatorAtIndex(index int, epoch uint64) { b.touchedLeaves[ValidatorsLeafIndex] = true - // change in validator set means cache purging - b.totalActiveBalanceCache = nil - return nil + if b.reverseChangeset != nil { + b.reverseChangeset.ExitEpochChange.AddChange(index, b.validators[index].ExitEpoch) + } + b.validators[index].ExitEpoch = epoch +} + +func (b *BeaconState) SetWithdrawableEpochForValidatorAtIndex(index int, epoch uint64) { + b.touchedLeaves[ValidatorsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.WithdrawalEpochChange.AddChange(index, b.validators[index].WithdrawableEpoch) + } + b.validators[index].WithdrawableEpoch = epoch +} + +func (b *BeaconState) SetEffectiveBalanceForValidatorAtIndex(index int, balance uint64) { + b.touchedLeaves[ValidatorsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.EffectiveBalanceChange.AddChange(index, b.validators[index].EffectiveBalance) + } + b.validators[index].EffectiveBalance = balance +} + +func (b *BeaconState) SetActivationEpochForValidatorAtIndex(index int, epoch uint64) { + b.touchedLeaves[ValidatorsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.ActivationEpochChange.AddChange(index, b.validators[index].ActivationEpoch) + } + b.validators[index].ActivationEpoch = epoch +} + +func (b *BeaconState) SetActivationEligibilityEpochForValidatorAtIndex(index int, epoch uint64) { + b.touchedLeaves[ValidatorsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.ActivationEligibilityEpochChange.AddChange(index, b.validators[index].ActivationEligibilityEpoch) + } + b.validators[index].ActivationEligibilityEpoch = epoch } func (b *BeaconState) SetEth1Data(eth1Data *cltypes.Eth1Data) { b.touchedLeaves[Eth1DataLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnEth1DataChange(eth1Data) + } b.eth1Data = eth1Data } @@ -82,12 +124,18 @@ func (b *BeaconState) AddEth1DataVote(vote *cltypes.Eth1Data) { } func (b *BeaconState) ResetEth1DataVotes() { + if b.reverseChangeset != nil { + b.reverseChangeset.Eth1DataVotesAtReset = b.eth1DataVotes + } b.touchedLeaves[Eth1DataVotesLeafIndex] = true - b.eth1DataVotes = b.eth1DataVotes[:0] + b.eth1DataVotes = nil } func (b *BeaconState) SetEth1DepositIndex(eth1DepositIndex uint64) { b.touchedLeaves[Eth1DepositIndexLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnEth1DepositIndexChange(b.eth1DepositIndex) + } b.eth1DepositIndex = eth1DepositIndex } @@ -120,72 +168,131 @@ func (b *BeaconState) SetValidatorBalance(index int, balance uint64) error { } b.touchedLeaves[BalancesLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.BalancesChanges.AddChange(index, b.balances[index]) + } b.balances[index] = balance return nil } func (b *BeaconState) SetRandaoMixAt(index int, mix libcommon.Hash) { b.touchedLeaves[RandaoMixesLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.RandaoMixesChanges.AddChange(index, b.randaoMixes[index]) + } b.randaoMixes[index] = mix } func (b *BeaconState) SetSlashingSegmentAt(index int, segment uint64) { b.touchedLeaves[SlashingsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.SlashingsChanges.AddChange(index, b.slashings[index]) + } b.slashings[index] = segment } -func (b *BeaconState) SetPreviousEpochParticipation(previousEpochParticipation []cltypes.ParticipationFlags) { +func (b *BeaconState) SetEpochParticipationForValidatorIndex(isCurrentEpoch bool, index int, flags cltypes.ParticipationFlags) { + if isCurrentEpoch { + if b.reverseChangeset != nil { + b.reverseChangeset.CurrentEpochParticipationChanges.AddChange(index, b.currentEpochParticipation[index]) + } + b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true + b.currentEpochParticipation[index] = flags + return + } + if b.reverseChangeset != nil { + b.reverseChangeset.PreviousEpochParticipationChanges.AddChange(index, b.previousEpochParticipation[index]) + } b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true - b.previousEpochParticipation = previousEpochParticipation + b.previousEpochParticipation[index] = flags +} + +func (b *BeaconState) SetValidatorAtIndex(index int, validator *cltypes.Validator) { + b.touchedLeaves[ValidatorsLeafIndex] = true + b.validators[index] = validator } -func (b *BeaconState) SetCurrentEpochParticipation(currentEpochParticipation []cltypes.ParticipationFlags) { +func (b *BeaconState) ResetEpochParticipation() { + if b.reverseChangeset != nil && len(b.reverseChangeset.CurrentEpochParticipationAtReset) == 0 && + len(b.reverseChangeset.PreviousEpochParticipationAtReset) == 0 { + b.reverseChangeset.CurrentEpochParticipationAtReset = b.currentEpochParticipation.Copy() + b.reverseChangeset.PreviousEpochParticipationAtReset = b.previousEpochParticipation.Copy() + } + b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true - b.currentEpochParticipation = currentEpochParticipation + b.previousEpochParticipation = b.currentEpochParticipation + b.currentEpochParticipation = make(cltypes.ParticipationFlagsList, len(b.validators)) } func (b *BeaconState) SetJustificationBits(justificationBits cltypes.JustificationBits) { b.touchedLeaves[JustificationBitsLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnJustificationBitsChange(b.justificationBits) + } b.justificationBits = justificationBits } func (b *BeaconState) SetPreviousJustifiedCheckpoint(previousJustifiedCheckpoint *cltypes.Checkpoint) { b.touchedLeaves[PreviousJustifiedCheckpointLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnPreviousJustifiedCheckpointChange(b.previousJustifiedCheckpoint) + } b.previousJustifiedCheckpoint = previousJustifiedCheckpoint } func (b *BeaconState) SetCurrentJustifiedCheckpoint(currentJustifiedCheckpoint *cltypes.Checkpoint) { b.touchedLeaves[CurrentJustifiedCheckpointLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnCurrentJustifiedCheckpointChange(b.currentJustifiedCheckpoint) + } b.currentJustifiedCheckpoint = currentJustifiedCheckpoint } func (b *BeaconState) SetFinalizedCheckpoint(finalizedCheckpoint *cltypes.Checkpoint) { b.touchedLeaves[FinalizedCheckpointLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnFinalizedCheckpointChange(b.finalizedCheckpoint) + } b.finalizedCheckpoint = finalizedCheckpoint } func (b *BeaconState) SetCurrentSyncCommittee(currentSyncCommittee *cltypes.SyncCommittee) { b.touchedLeaves[CurrentSyncCommitteeLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnCurrentSyncCommitteeChange(b.currentSyncCommittee) + } b.currentSyncCommittee = currentSyncCommittee } func (b *BeaconState) SetNextSyncCommittee(nextSyncCommittee *cltypes.SyncCommittee) { b.touchedLeaves[NextSyncCommitteeLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnNextSyncCommitteeChange(b.nextSyncCommittee) + } b.nextSyncCommittee = nextSyncCommittee } func (b *BeaconState) SetLatestExecutionPayloadHeader(header *cltypes.Eth1Header) { b.touchedLeaves[LatestExecutionPayloadHeaderLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnEth1Header(b.latestExecutionPayloadHeader) + } b.latestExecutionPayloadHeader = header } func (b *BeaconState) SetNextWithdrawalIndex(index uint64) { b.touchedLeaves[NextWithdrawalIndexLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnNextWithdrawalIndexChange(b.nextWithdrawalIndex) + } b.nextWithdrawalIndex = index } func (b *BeaconState) SetNextWithdrawalValidatorIndex(index uint64) { b.touchedLeaves[NextWithdrawalValidatorIndexLeafIndex] = true + if b.reverseChangeset != nil { + b.reverseChangeset.OnNextWithdrawalValidatorIndexChange(b.nextWithdrawalValidatorIndex) + } b.nextWithdrawalValidatorIndex = index } @@ -208,55 +315,40 @@ func (b *BeaconState) SetValidatorInactivityScore(index int, score uint64) error if index >= len(b.inactivityScores) { return ErrInvalidValidatorIndex } + if b.reverseChangeset != nil { + b.reverseChangeset.InactivityScoresChanges.AddChange(index, b.inactivityScores[index]) + } b.touchedLeaves[InactivityScoresLeafIndex] = true b.inactivityScores[index] = score return nil } 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") - } +func (b *BeaconState) AddPreviousEpochAttestation(attestation *cltypes.PendingAttestation) { 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") - } +func (b *BeaconState) ResetCurrentEpochAttestations() { b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true - b.currentEpochAttestations = attestations + b.currentEpochAttestations = nil } -func (b *BeaconState) SetPreviousEpochAtteastations(attestations []*cltypes.PendingAttestation) { - if b.version != clparams.Phase0Version { - panic("can call SetPreviousEpochAtteastations only on phase0") - } +func (b *BeaconState) SetPreviousEpochAttestations(attestations []*cltypes.PendingAttestation) { b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true b.previousEpochAttestations = attestations } diff --git a/cmd/erigon-cl/core/state/state.go b/cmd/erigon-cl/core/state/state.go index c3a24fa5abf..d36ab70d841 100644 --- a/cmd/erigon-cl/core/state/state.go +++ b/cmd/erigon-cl/core/state/state.go @@ -11,6 +11,7 @@ import ( "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/beacon_changeset" ) type HashFunc func([]byte) ([32]byte, error) @@ -73,6 +74,8 @@ type BeaconState struct { previousStateRoot libcommon.Hash // Configs beaconConfig *clparams.BeaconChainConfig + // Changesets + reverseChangeset *beacon_changeset.ReverseBeaconStateChangeSet } func New(cfg *clparams.BeaconChainConfig) *BeaconState { diff --git a/cmd/erigon-cl/core/state/upgrade.go b/cmd/erigon-cl/core/state/upgrade.go index 2ad0a126ae6..5f83423baaf 100644 --- a/cmd/erigon-cl/core/state/upgrade.go +++ b/cmd/erigon-cl/core/state/upgrade.go @@ -59,6 +59,10 @@ func (b *BeaconState) UpgradeToAltair() error { func (b *BeaconState) UpgradeToBellatrix() error { b.previousStateRoot = libcommon.Hash{} epoch := b.Epoch() + if b.reverseChangeset != nil { + b.reverseChangeset.OnVersionChange(b.version) + b.reverseChangeset.OnForkChange(b.fork) + } // update version b.fork.Epoch = epoch b.fork.PreviousVersion = b.fork.CurrentVersion @@ -74,6 +78,11 @@ func (b *BeaconState) UpgradeToBellatrix() error { func (b *BeaconState) UpgradeToCapella() error { b.previousStateRoot = libcommon.Hash{} epoch := b.Epoch() + if b.reverseChangeset != nil { + b.reverseChangeset.OnVersionChange(b.version) + b.reverseChangeset.OnForkChange(b.fork) + b.reverseChangeset.OnEth1Header(b.latestExecutionPayloadHeader) + } // update version b.fork.Epoch = epoch b.fork.PreviousVersion = b.fork.CurrentVersion diff --git a/cmd/erigon-cl/core/transition/finalization_and_justification.go b/cmd/erigon-cl/core/transition/finalization_and_justification.go index 5d4a9e11364..1a52abeed55 100644 --- a/cmd/erigon-cl/core/transition/finalization_and_justification.go +++ b/cmd/erigon-cl/core/transition/finalization_and_justification.go @@ -84,7 +84,7 @@ func ProcessJustificationBitsAndFinality(state *state.BeaconState) error { } } else { // Use bitlists to determine finality. - previousParticipation, currentParticipation := state.PreviousEpochParticipation(), state.CurrentEpochParticipation() + previousParticipation, currentParticipation := state.EpochParticipation(false), state.EpochParticipation(true) for i, validator := range state.Validators() { if validator.Slashed { continue diff --git a/cmd/erigon-cl/core/transition/operations.go b/cmd/erigon-cl/core/transition/operations.go index ca572e29da8..6bed19bd821 100644 --- a/cmd/erigon-cl/core/transition/operations.go +++ b/cmd/erigon-cl/core/transition/operations.go @@ -26,7 +26,7 @@ func isValidIndexedAttestation(state *state.BeaconState, att *cltypes.IndexedAtt pks := [][]byte{} for _, v := range inds { - val, err := state.ValidatorAt(int(v)) + val, err := state.ValidatorForValidatorIndex(int(v)) if err != nil { return false, err } @@ -77,7 +77,7 @@ func ProcessProposerSlashing(state *state.BeaconState, propSlashing *cltypes.Pro return fmt.Errorf("propose slashing headers are the same: %v == %v", h1Root, h2Root) } - proposer, err := state.ValidatorAt(int(h1.ProposerIndex)) + proposer, err := state.ValidatorForValidatorIndex(int(h1.ProposerIndex)) if err != nil { return err } @@ -135,7 +135,7 @@ func ProcessAttesterSlashing(state *state.BeaconState, attSlashing *cltypes.Atte slashedAny := false currentEpoch := state.GetEpochAtSlot(state.Slot()) for _, ind := range utils.IntersectionOfSortedSets(att1.AttestingIndices, att2.AttestingIndices) { - validator, err := state.ValidatorAt(int(ind)) + validator, err := state.ValidatorForValidatorIndex(int(ind)) if err != nil { return err } @@ -217,7 +217,7 @@ func ProcessVoluntaryExit(state *state.BeaconState, signedVoluntaryExit *cltypes // Sanity checks so that we know it is good. voluntaryExit := signedVoluntaryExit.VolunaryExit currentEpoch := state.Epoch() - validator, err := state.ValidatorAt(int(voluntaryExit.ValidatorIndex)) + validator, err := state.ValidatorForValidatorIndex(int(voluntaryExit.ValidatorIndex)) if err != nil { return err } diff --git a/cmd/erigon-cl/core/transition/operations_test.go b/cmd/erigon-cl/core/transition/operations_test.go index 490720ab481..bd86031fd57 100644 --- a/cmd/erigon-cl/core/transition/operations_test.go +++ b/cmd/erigon-cl/core/transition/operations_test.go @@ -78,7 +78,7 @@ func getSuccessfulAttesterSlashing() *cltypes.AttesterSlashing { func TestProcessProposerSlashing(t *testing.T) { unchangingState := getTestState(t) - unchangingState.SetValidatorAt(propInd, &cltypes.Validator{ + unchangingState.SetValidatorAtIndex(propInd, &cltypes.Validator{ Slashed: false, ActivationEpoch: 0, WithdrawableEpoch: 10000, @@ -86,7 +86,7 @@ func TestProcessProposerSlashing(t *testing.T) { }) successState := getTestState(t) - successState.SetValidatorAt(propInd, &cltypes.Validator{ + successState.SetValidatorAtIndex(propInd, &cltypes.Validator{ Slashed: false, ActivationEpoch: 0, WithdrawableEpoch: 10000, @@ -187,13 +187,13 @@ func TestProcessProposerSlashing(t *testing.T) { func TestProcessAttesterSlashing(t *testing.T) { unchangingState := getTestState(t) - unchangingState.SetValidatorAt(0, &cltypes.Validator{ + unchangingState.SetValidatorAtIndex(0, &cltypes.Validator{ Slashed: false, ActivationEpoch: 0, WithdrawableEpoch: 10000, PublicKey: testPublicKeySlashing, }) - unchangingState.SetValidatorAt(1, &cltypes.Validator{ + unchangingState.SetValidatorAtIndex(1, &cltypes.Validator{ Slashed: false, ActivationEpoch: 0, WithdrawableEpoch: 10000, @@ -201,13 +201,13 @@ func TestProcessAttesterSlashing(t *testing.T) { }) successState := getTestState(t) - successState.SetValidatorAt(0, &cltypes.Validator{ + successState.SetValidatorAtIndex(0, &cltypes.Validator{ Slashed: false, ActivationEpoch: 0, WithdrawableEpoch: 10000, PublicKey: testPublicKeySlashing, }) - successState.SetValidatorAt(1, &cltypes.Validator{ + successState.SetValidatorAtIndex(1, &cltypes.Validator{ Slashed: false, ActivationEpoch: 0, WithdrawableEpoch: 10000, diff --git a/cmd/erigon-cl/core/transition/process_attestations.go b/cmd/erigon-cl/core/transition/process_attestations.go index 9ae5d6edbb0..8bcece04b71 100644 --- a/cmd/erigon-cl/core/transition/process_attestations.go +++ b/cmd/erigon-cl/core/transition/process_attestations.go @@ -52,21 +52,17 @@ func processAttestationPostAltair(state *state.BeaconState, attestation *cltypes } var proposerRewardNumerator uint64 - var epochParticipation cltypes.ParticipationFlagsList - if data.Target.Epoch == currentEpoch { - epochParticipation = state.CurrentEpochParticipation() - } else { - epochParticipation = state.PreviousEpochParticipation() - } - validators := state.Validators() + isCurrentEpoch := data.Target.Epoch == currentEpoch + validators := state.Validators() for _, attesterIndex := range attestingIndicies { baseReward := (validators[attesterIndex].EffectiveBalance / beaconConfig.EffectiveBalanceIncrement) * baseRewardPerIncrement for flagIndex, weight := range beaconConfig.ParticipationWeights() { - if !slices.Contains(participationFlagsIndicies, uint8(flagIndex)) || epochParticipation[attesterIndex].HasFlag(flagIndex) { + flagParticipation := state.EpochParticipationForValidatorIndex(isCurrentEpoch, int(attesterIndex)) + if !slices.Contains(participationFlagsIndicies, uint8(flagIndex)) || flagParticipation.HasFlag(flagIndex) { continue } - epochParticipation[attesterIndex] = epochParticipation[attesterIndex].Add(flagIndex) + state.SetEpochParticipationForValidatorIndex(isCurrentEpoch, int(attesterIndex), flagParticipation.Add(flagIndex)) proposerRewardNumerator += baseReward * weight } } @@ -75,12 +71,6 @@ func processAttestationPostAltair(state *state.BeaconState, attestation *cltypes if err != nil { return nil, err } - // Set participation - if data.Target.Epoch == currentEpoch { - state.SetCurrentEpochParticipation(epochParticipation) - } else { - state.SetPreviousEpochParticipation(epochParticipation) - } proposerRewardDenominator := (beaconConfig.WeightDenominator - beaconConfig.ProposerWeight) * beaconConfig.WeightDenominator / beaconConfig.ProposerWeight reward := proposerRewardNumerator / proposerRewardDenominator return attestingIndicies, state.IncreaseBalance(proposer, reward) @@ -120,7 +110,7 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att if !data.Source.Equal(state.PreviousJustifiedCheckpoint()) { return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") } - state.AddPreviousEpochAtteastation(pendingAttestation) + state.AddPreviousEpochAttestation(pendingAttestation) } // Not required by specs but needed if we want performant epoch transition. indicies, err := state.GetAttestingIndicies(attestation.Data, attestation.AggregationBits, true) @@ -137,7 +127,7 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att } // 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)) + validator, err := state.ValidatorForValidatorIndex(int(index)) if err != nil { return nil, err } @@ -169,10 +159,6 @@ func processAttestationPhase0(state *state.BeaconState, attestation *cltypes.Att validator.IsPreviousMatchingHeadAttester = true } } - - if err := state.SetValidatorAt(int(index), validator); err != nil { - return nil, err - } } return indicies, nil } diff --git a/cmd/erigon-cl/core/transition/process_bls_to_execution_change.go b/cmd/erigon-cl/core/transition/process_bls_to_execution_change.go index 2830186b07a..9b0e0686f59 100644 --- a/cmd/erigon-cl/core/transition/process_bls_to_execution_change.go +++ b/cmd/erigon-cl/core/transition/process_bls_to_execution_change.go @@ -16,7 +16,7 @@ func ProcessBlsToExecutionChange(state *state.BeaconState, signedChange *cltypes change := signedChange.Message beaconConfig := state.BeaconConfig() - validator, err := state.ValidatorAt(int(change.ValidatorIndex)) + validator, err := state.ValidatorForValidatorIndex(int(change.ValidatorIndex)) if err != nil { return err } @@ -51,12 +51,13 @@ func ProcessBlsToExecutionChange(state *state.BeaconState, signedChange *cltypes return fmt.Errorf("invalid signature") } } - + credentials := validator.WithdrawalCredentials // Reset the validator's withdrawal credentials. - validator.WithdrawalCredentials[0] = beaconConfig.ETH1AddressWithdrawalPrefixByte - copy(validator.WithdrawalCredentials[1:], make([]byte, 11)) - copy(validator.WithdrawalCredentials[12:], change.To[:]) + credentials[0] = beaconConfig.ETH1AddressWithdrawalPrefixByte + copy(credentials[1:], make([]byte, 11)) + copy(credentials[12:], change.To[:]) // Update the state with the modified validator. - return state.SetValidatorAt(int(change.ValidatorIndex), validator) + state.SetWithdrawalCredentialForValidatorAtIndex(int(change.ValidatorIndex), credentials) + return nil } diff --git a/cmd/erigon-cl/core/transition/process_effective_balance_update.go b/cmd/erigon-cl/core/transition/process_effective_balance_update.go index cdcb15ae31b..a6c9d58508f 100644 --- a/cmd/erigon-cl/core/transition/process_effective_balance_update.go +++ b/cmd/erigon-cl/core/transition/process_effective_balance_update.go @@ -21,10 +21,8 @@ func ProcessEffectiveBalanceUpdates(state *state.BeaconState) error { if balance+downwardThreshold < validator.EffectiveBalance || validator.EffectiveBalance+upwardThreshold < balance { // Set new effective balance - validator.EffectiveBalance = utils.Min64(balance-(balance%beaconConfig.EffectiveBalanceIncrement), beaconConfig.MaxEffectiveBalance) - if err := state.SetValidatorAt(index, validator); err != nil { - return err - } + effectiveBalance := utils.Min64(balance-(balance%beaconConfig.EffectiveBalanceIncrement), beaconConfig.MaxEffectiveBalance) + state.SetEffectiveBalanceForValidatorAtIndex(index, effectiveBalance) } } return nil diff --git a/cmd/erigon-cl/core/transition/process_epoch.go b/cmd/erigon-cl/core/transition/process_epoch.go index 61f61c5cf06..77fd00e4aa9 100644 --- a/cmd/erigon-cl/core/transition/process_epoch.go +++ b/cmd/erigon-cl/core/transition/process_epoch.go @@ -49,10 +49,10 @@ func ProcessEpoch(state *state.BeaconState) error { } func ProcessParticipationRecordUpdates(state *state.BeaconState) error { - state.SetPreviousEpochAtteastations(state.CurrentEpochAttestations()) - state.SetCurrentEpochAtteastations(nil) + state.SetPreviousEpochAttestations(state.CurrentEpochAttestations()) + state.ResetCurrentEpochAttestations() // Also mark all current attesters as previous - for validatorIndex, validator := range state.Validators() { + for _, validator := range state.Validators() { // Previous sources/target/head validator.IsPreviousMatchingSourceAttester = validator.IsCurrentMatchingSourceAttester validator.IsPreviousMatchingTargetAttester = validator.IsCurrentMatchingTargetAttester @@ -63,10 +63,6 @@ func ProcessParticipationRecordUpdates(state *state.BeaconState) error { validator.IsCurrentMatchingSourceAttester = false validator.IsCurrentMatchingTargetAttester = false validator.IsCurrentMatchingHeadAttester = false - // Setting the validator - if err := state.SetValidatorAt(validatorIndex, validator); err != nil { - return err - } } return nil } diff --git a/cmd/erigon-cl/core/transition/process_registry_updates.go b/cmd/erigon-cl/core/transition/process_registry_updates.go index 2a31975e81c..90af5b472ef 100644 --- a/cmd/erigon-cl/core/transition/process_registry_updates.go +++ b/cmd/erigon-cl/core/transition/process_registry_updates.go @@ -22,10 +22,7 @@ func ProcessRegistryUpdates(state *state.BeaconState) error { // Process activation eligibility and ejections. for validatorIndex, validator := range validators { if state.IsValidatorEligibleForActivationQueue(validator) { - validator.ActivationEligibilityEpoch = currentEpoch + 1 - if err := state.SetValidatorAt(validatorIndex, validator); err != nil { - return err - } + state.SetActivationEligibilityEpochForValidatorAtIndex(validatorIndex, currentEpoch+1) } if validator.Active(currentEpoch) && validator.EffectiveBalance <= beaconConfig.EjectionBalance { if err := state.InitiateValidatorExit(uint64(validatorIndex)); err != nil { @@ -51,14 +48,7 @@ func ProcessRegistryUpdates(state *state.BeaconState) error { } // Only process up to epoch limit. for _, validatorIndex := range activationQueue { - validator, err := state.ValidatorAt(int(validatorIndex)) - if err != nil { - return err - } - validator.ActivationEpoch = computeActivationExitEpoch(beaconConfig, currentEpoch) - if err := state.SetValidatorAt(int(validatorIndex), validator); err != nil { - return err - } + state.SetActivationEpochForValidatorAtIndex(int(validatorIndex), computeActivationExitEpoch(beaconConfig, currentEpoch)) } return nil } diff --git a/cmd/erigon-cl/core/transition/process_slots.go b/cmd/erigon-cl/core/transition/process_slots.go index a99e68afdce..4947d393082 100644 --- a/cmd/erigon-cl/core/transition/process_slots.go +++ b/cmd/erigon-cl/core/transition/process_slots.go @@ -70,10 +70,11 @@ func transitionSlot(state *state.BeaconState) error { latestBlockHeader := state.LatestBlockHeader() if latestBlockHeader.Root == [32]byte{} { latestBlockHeader.Root = previousStateRoot - state.SetLatestBlockHeader(latestBlockHeader) + state.SetLatestBlockHeader(&latestBlockHeader) } + blockHeader := state.LatestBlockHeader() - previousBlockRoot, err := state.LatestBlockHeader().HashSSZ() + previousBlockRoot, err := (&blockHeader).HashSSZ() if err != nil { return err } @@ -127,7 +128,7 @@ func ProcessSlots(state *state.BeaconState, slot uint64) error { } func verifyBlockSignature(state *state.BeaconState, block *cltypes.SignedBeaconBlock) (bool, error) { - proposer, err := state.ValidatorAt(int(block.Block.ProposerIndex)) + proposer, err := state.ValidatorForValidatorIndex(int(block.Block.ProposerIndex)) if err != nil { return false, err } diff --git a/cmd/erigon-cl/core/transition/process_slots_test.go b/cmd/erigon-cl/core/transition/process_slots_test.go index 58fecb76cf2..dc7b91549af 100644 --- a/cmd/erigon-cl/core/transition/process_slots_test.go +++ b/cmd/erigon-cl/core/transition/process_slots_test.go @@ -54,7 +54,7 @@ func prepareNextBeaconState(t *testing.T, slots []uint64, stateHashs, blockHashs // Only copy if the previous is empty. if latestBlockHeader.Root == [32]byte{} { latestBlockHeader.Root = libcommon.BytesToHash(hash) - nextState.SetLatestBlockHeader(latestBlockHeader) + nextState.SetLatestBlockHeader(&latestBlockHeader) } hash, err = hex.DecodeString(blockHashs[i]) if err != nil { diff --git a/cmd/erigon-cl/core/transition/processing.go b/cmd/erigon-cl/core/transition/processing.go index 72bc1fecdc1..a660af7e643 100644 --- a/cmd/erigon-cl/core/transition/processing.go +++ b/cmd/erigon-cl/core/transition/processing.go @@ -33,7 +33,8 @@ func ProcessBlockHeader(state *state.BeaconState, block *cltypes.BeaconBlock, fu if block.ProposerIndex != propInd { return fmt.Errorf("block proposer index: %d, does not match beacon proposer index: %d", block.ProposerIndex, propInd) } - latestRoot, err := state.LatestBlockHeader().HashSSZ() + blockHeader := state.LatestBlockHeader() + latestRoot, err := (&blockHeader).HashSSZ() if err != nil { return fmt.Errorf("unable to hash tree root of latest block header: %v", err) } @@ -53,7 +54,7 @@ func ProcessBlockHeader(state *state.BeaconState, block *cltypes.BeaconBlock, fu BodyRoot: bodyRoot, }) - proposer, err := state.ValidatorAt(int(block.ProposerIndex)) + proposer, err := state.ValidatorForValidatorIndex(int(block.ProposerIndex)) if err != nil { return err } @@ -65,7 +66,7 @@ func ProcessBlockHeader(state *state.BeaconState, block *cltypes.BeaconBlock, fu func ProcessRandao(state *state.BeaconState, randao [96]byte, proposerIndex uint64, fullValidation bool) error { epoch := state.Epoch() - proposer, err := state.ValidatorAt(int(proposerIndex)) + proposer, err := state.ValidatorForValidatorIndex(int(proposerIndex)) if err != nil { return err } @@ -93,7 +94,6 @@ func ProcessRandao(state *state.BeaconState, randao [96]byte, proposerIndex uint for i := range mix { mix[i] = randaoMixes[i] ^ randaoHash[i] } - state.SetRandaoMixAt(int(epoch%state.BeaconConfig().EpochsPerHistoricalVector), mix) return nil } diff --git a/cmd/erigon-cl/core/transition/processing_test.go b/cmd/erigon-cl/core/transition/processing_test.go index b1f041d2b85..6ea60b5492e 100644 --- a/cmd/erigon-cl/core/transition/processing_test.go +++ b/cmd/erigon-cl/core/transition/processing_test.go @@ -83,10 +83,10 @@ func TestProcessBlockHeader(t *testing.T) { badBlockBodyHash.Body.Attestations = append(badBlockBodyHash.Body.Attestations, &cltypes.Attestation{}) badStateSlashed := getTestState(t) - validator, err := badStateSlashed.ValidatorAt(int(testBlock.ProposerIndex)) + validator, err := badStateSlashed.ValidatorForValidatorIndex(int(testBlock.ProposerIndex)) require.NoError(t, err) validator.Slashed = true - badStateSlashed.SetValidatorAt(int(testBlock.ProposerIndex), validator) + badStateSlashed.SetValidatorAtIndex(int(testBlock.ProposerIndex), validator) testCases := []struct { description string @@ -160,10 +160,10 @@ func TestProcessRandao(t *testing.T) { if err != nil { t.Fatalf("unable to get proposer index: %v", err) } - validator, err := testStateSuccess.ValidatorAt(int(propInd)) + validator, err := testStateSuccess.ValidatorForValidatorIndex(int(propInd)) require.NoError(t, err) validator.PublicKey = testPublicKeyRandao - testStateSuccess.SetValidatorAt(int(propInd), validator) + testStateSuccess.SetValidatorAtIndex(int(propInd), validator) testBlock := getTestBlock(t) testBlock.Body.RandaoReveal = testSignatureRandao testBody := testBlock.Body diff --git a/cmd/erigon-cl/core/transition/resets.go b/cmd/erigon-cl/core/transition/resets.go index 1de17c4b197..10b0564391c 100644 --- a/cmd/erigon-cl/core/transition/resets.go +++ b/cmd/erigon-cl/core/transition/resets.go @@ -1,7 +1,6 @@ package transition import ( - "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state" ) @@ -24,6 +23,5 @@ func ProcessRandaoMixesReset(state *state.BeaconState) { } func ProcessParticipationFlagUpdates(state *state.BeaconState) { - state.SetPreviousEpochParticipation(state.CurrentEpochParticipation()) - state.SetCurrentEpochParticipation(make([]cltypes.ParticipationFlags, len(state.Validators()))) + state.ResetEpochParticipation() } diff --git a/cmd/erigon-cl/stages/stage_beacon_blocks.go b/cmd/erigon-cl/stages/stage_beacon_blocks.go index 4c32c838487..4baa8b1ebfb 100644 --- a/cmd/erigon-cl/stages/stage_beacon_blocks.go +++ b/cmd/erigon-cl/stages/stage_beacon_blocks.go @@ -64,9 +64,8 @@ func SpawnStageBeaconsBlocks(cfg StageBeaconsBlockCfg, s *stagedsync.StageState, log.Info(fmt.Sprintf("[%s] Started", s.LogPrefix()), "start", progress, "target", targetSlot) cfg.downloader.SetHighestProcessedSlot(progress) - if cfg.downloader.HighestProcessedRoot() == (libcommon.Hash{}) { - cfg.downloader.SetHighestProcessedRoot(lastRoot) - } + cfg.downloader.SetHighestProcessedRoot(lastRoot) + cfg.downloader.SetTargetSlot(targetSlot) cfg.downloader.SetLimitSegmentsLength(1024) // On new blocks we just check slot sequencing for now :)