diff --git a/beacon-chain/core/capella/BUILD.bazel b/beacon-chain/core/capella/BUILD.bazel new file mode 100644 index 0000000000..8c7a49b6f1 --- /dev/null +++ b/beacon-chain/core/capella/BUILD.bazel @@ -0,0 +1,31 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["upgrade.go"], + importpath = "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/capella", + visibility = ["//visibility:public"], + deps = [ + "//beacon-chain/core/time:go_default_library", + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/state-native:go_default_library", + "//config/params:go_default_library", + "//proto/engine/v1:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["upgrade_test.go"], + deps = [ + ":go_default_library", + "//beacon-chain/core/time:go_default_library", + "//config/params:go_default_library", + "//consensus-types/primitives:go_default_library", + "//proto/engine/v1:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//testing/require:go_default_library", + "//testing/util:go_default_library", + ], +) diff --git a/beacon-chain/core/capella/upgrade.go b/beacon-chain/core/capella/upgrade.go new file mode 100644 index 0000000000..013ee403b6 --- /dev/null +++ b/beacon-chain/core/capella/upgrade.go @@ -0,0 +1,96 @@ +package capella + +import ( + "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/time" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" + state_native "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v3/config/params" + enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" +) + +// UpgradeToCapella updates a generic state to return the version Capella state. +func UpgradeToCapella(state state.BeaconState) (state.BeaconState, error) { + epoch := time.CurrentEpoch(state) + + currentSyncCommittee, err := state.CurrentSyncCommittee() + if err != nil { + return nil, err + } + nextSyncCommittee, err := state.NextSyncCommittee() + if err != nil { + return nil, err + } + prevEpochParticipation, err := state.PreviousEpochParticipation() + if err != nil { + return nil, err + } + currentEpochParticipation, err := state.CurrentEpochParticipation() + if err != nil { + return nil, err + } + inactivityScores, err := state.InactivityScores() + if err != nil { + return nil, err + } + payloadHeader, err := state.LatestExecutionPayloadHeader() + if err != nil { + return nil, err + } + txRoot, err := payloadHeader.TransactionsRoot() + if err != nil { + return nil, err + } + + s := ðpb.BeaconStateCapella{ + GenesisTime: state.GenesisTime(), + GenesisValidatorsRoot: state.GenesisValidatorsRoot(), + Slot: state.Slot(), + Fork: ðpb.Fork{ + PreviousVersion: state.Fork().CurrentVersion, + CurrentVersion: params.BeaconConfig().CapellaForkVersion, + Epoch: epoch, + }, + LatestBlockHeader: state.LatestBlockHeader(), + BlockRoots: state.BlockRoots(), + StateRoots: state.StateRoots(), + HistoricalRoots: state.HistoricalRoots(), + Eth1Data: state.Eth1Data(), + Eth1DataVotes: state.Eth1DataVotes(), + Eth1DepositIndex: state.Eth1DepositIndex(), + Validators: state.Validators(), + Balances: state.Balances(), + RandaoMixes: state.RandaoMixes(), + Slashings: state.Slashings(), + PreviousEpochParticipation: prevEpochParticipation, + CurrentEpochParticipation: currentEpochParticipation, + JustificationBits: state.JustificationBits(), + PreviousJustifiedCheckpoint: state.PreviousJustifiedCheckpoint(), + CurrentJustifiedCheckpoint: state.CurrentJustifiedCheckpoint(), + FinalizedCheckpoint: state.FinalizedCheckpoint(), + InactivityScores: inactivityScores, + CurrentSyncCommittee: currentSyncCommittee, + NextSyncCommittee: nextSyncCommittee, + LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderCapella{ + ParentHash: payloadHeader.ParentHash(), + FeeRecipient: payloadHeader.FeeRecipient(), + StateRoot: payloadHeader.StateRoot(), + ReceiptsRoot: payloadHeader.ReceiptsRoot(), + LogsBloom: payloadHeader.LogsBloom(), + PrevRandao: payloadHeader.PrevRandao(), + BlockNumber: payloadHeader.BlockNumber(), + GasLimit: payloadHeader.GasLimit(), + GasUsed: payloadHeader.GasUsed(), + Timestamp: payloadHeader.Timestamp(), + ExtraData: payloadHeader.ExtraData(), + BaseFeePerGas: payloadHeader.BaseFeePerGas(), + BlockHash: payloadHeader.BlockHash(), + TransactionsRoot: txRoot, + WithdrawalsRoot: make([]byte, 32), + }, + NextWithdrawalIndex: 0, + LastWithdrawalValidatorIndex: 0, + } + + return state_native.InitializeFromProtoUnsafeCapella(s) +} diff --git a/beacon-chain/core/capella/upgrade_test.go b/beacon-chain/core/capella/upgrade_test.go new file mode 100644 index 0000000000..7580bb8d01 --- /dev/null +++ b/beacon-chain/core/capella/upgrade_test.go @@ -0,0 +1,101 @@ +package capella_test + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/capella" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/time" + "github.com/prysmaticlabs/prysm/v3/config/params" + types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" + enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v3/testing/require" + "github.com/prysmaticlabs/prysm/v3/testing/util" +) + +func TestUpgradeToCapella(t *testing.T) { + st, _ := util.DeterministicGenesisStateBellatrix(t, params.BeaconConfig().MaxValidatorsPerCommittee) + preForkState := st.Copy() + mSt, err := capella.UpgradeToCapella(st) + require.NoError(t, err) + + require.Equal(t, preForkState.GenesisTime(), mSt.GenesisTime()) + require.DeepSSZEqual(t, preForkState.GenesisValidatorsRoot(), mSt.GenesisValidatorsRoot()) + require.Equal(t, preForkState.Slot(), mSt.Slot()) + require.DeepSSZEqual(t, preForkState.LatestBlockHeader(), mSt.LatestBlockHeader()) + require.DeepSSZEqual(t, preForkState.BlockRoots(), mSt.BlockRoots()) + require.DeepSSZEqual(t, preForkState.StateRoots(), mSt.StateRoots()) + require.DeepSSZEqual(t, preForkState.HistoricalRoots(), mSt.HistoricalRoots()) + require.DeepSSZEqual(t, preForkState.Eth1Data(), mSt.Eth1Data()) + require.DeepSSZEqual(t, preForkState.Eth1DataVotes(), mSt.Eth1DataVotes()) + require.DeepSSZEqual(t, preForkState.Eth1DepositIndex(), mSt.Eth1DepositIndex()) + require.DeepSSZEqual(t, preForkState.Validators(), mSt.Validators()) + require.DeepSSZEqual(t, preForkState.Balances(), mSt.Balances()) + require.DeepSSZEqual(t, preForkState.RandaoMixes(), mSt.RandaoMixes()) + require.DeepSSZEqual(t, preForkState.Slashings(), mSt.Slashings()) + require.DeepSSZEqual(t, preForkState.JustificationBits(), mSt.JustificationBits()) + require.DeepSSZEqual(t, preForkState.PreviousJustifiedCheckpoint(), mSt.PreviousJustifiedCheckpoint()) + require.DeepSSZEqual(t, preForkState.CurrentJustifiedCheckpoint(), mSt.CurrentJustifiedCheckpoint()) + require.DeepSSZEqual(t, preForkState.FinalizedCheckpoint(), mSt.FinalizedCheckpoint()) + numValidators := mSt.NumValidators() + p, err := mSt.PreviousEpochParticipation() + require.NoError(t, err) + require.DeepSSZEqual(t, make([]byte, numValidators), p) + p, err = mSt.CurrentEpochParticipation() + require.NoError(t, err) + require.DeepSSZEqual(t, make([]byte, numValidators), p) + s, err := mSt.InactivityScores() + require.NoError(t, err) + require.DeepSSZEqual(t, make([]uint64, numValidators), s) + + f := mSt.Fork() + require.DeepSSZEqual(t, ðpb.Fork{ + PreviousVersion: st.Fork().CurrentVersion, + CurrentVersion: params.BeaconConfig().CapellaForkVersion, + Epoch: time.CurrentEpoch(st), + }, f) + csc, err := mSt.CurrentSyncCommittee() + require.NoError(t, err) + psc, err := preForkState.CurrentSyncCommittee() + require.NoError(t, err) + require.DeepSSZEqual(t, psc, csc) + nsc, err := mSt.NextSyncCommittee() + require.NoError(t, err) + psc, err = preForkState.NextSyncCommittee() + require.NoError(t, err) + require.DeepSSZEqual(t, psc, nsc) + + header, err := mSt.LatestExecutionPayloadHeader() + require.NoError(t, err) + protoHeader, ok := header.Proto().(*enginev1.ExecutionPayloadHeaderCapella) + require.Equal(t, true, ok) + prevHeader, err := preForkState.LatestExecutionPayloadHeader() + require.NoError(t, err) + txRoot, err := prevHeader.TransactionsRoot() + require.NoError(t, err) + + wanted := &enginev1.ExecutionPayloadHeaderCapella{ + ParentHash: prevHeader.ParentHash(), + FeeRecipient: prevHeader.FeeRecipient(), + StateRoot: prevHeader.StateRoot(), + ReceiptsRoot: prevHeader.ReceiptsRoot(), + LogsBloom: prevHeader.LogsBloom(), + PrevRandao: prevHeader.PrevRandao(), + BlockNumber: prevHeader.BlockNumber(), + GasLimit: prevHeader.GasLimit(), + GasUsed: prevHeader.GasUsed(), + Timestamp: prevHeader.Timestamp(), + BaseFeePerGas: prevHeader.BaseFeePerGas(), + BlockHash: prevHeader.BlockHash(), + TransactionsRoot: txRoot, + WithdrawalsRoot: make([]byte, 32), + } + require.DeepEqual(t, wanted, protoHeader) + nwi, err := mSt.NextWithdrawalIndex() + require.NoError(t, err) + require.Equal(t, uint64(0), nwi) + + lwvi, err := mSt.LastWithdrawalValidatorIndex() + require.NoError(t, err) + require.Equal(t, types.ValidatorIndex(0), lwvi) +} diff --git a/beacon-chain/core/time/slot_epoch.go b/beacon-chain/core/time/slot_epoch.go index fa10276c26..41989ec28a 100644 --- a/beacon-chain/core/time/slot_epoch.go +++ b/beacon-chain/core/time/slot_epoch.go @@ -70,6 +70,15 @@ func CanUpgradeToBellatrix(slot types.Slot) bool { return epochStart && bellatrixEpoch } +// CanUpgradeToCapella returns true if the input `slot` can upgrade to Capella. +// Spec code: +// If state.slot % SLOTS_PER_EPOCH == 0 and compute_epoch_at_slot(state.slot) == CAPELLA_FORK_EPOCH +func CanUpgradeToCapella(slot types.Slot) bool { + epochStart := slots.IsEpochStart(slot) + capellaEpoch := slots.ToEpoch(slot) == params.BeaconConfig().CapellaForkEpoch + return epochStart && capellaEpoch +} + // CanProcessEpoch checks the eligibility to process epoch. // The epoch can be processed at the end of the last slot of every epoch. // diff --git a/beacon-chain/core/time/slot_epoch_test.go b/beacon-chain/core/time/slot_epoch_test.go index a8bb6757af..cbcf8185df 100644 --- a/beacon-chain/core/time/slot_epoch_test.go +++ b/beacon-chain/core/time/slot_epoch_test.go @@ -263,3 +263,38 @@ func TestAltairCompatible(t *testing.T) { }) } } + +func TestCanUpgradeToCapella(t *testing.T) { + params.SetupTestConfigCleanup(t) + bc := params.BeaconConfig() + bc.CapellaForkEpoch = 5 + params.OverrideBeaconConfig(bc) + tests := []struct { + name string + slot types.Slot + want bool + }{ + { + name: "not epoch start", + slot: 1, + want: false, + }, + { + name: "not capella epoch", + slot: params.BeaconConfig().SlotsPerEpoch, + want: false, + }, + { + name: "capella epoch", + slot: types.Slot(params.BeaconConfig().CapellaForkEpoch) * params.BeaconConfig().SlotsPerEpoch, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := time.CanUpgradeToCapella(tt.slot); got != tt.want { + t.Errorf("CanUpgradeToCapella() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/beacon-chain/core/transition/BUILD.bazel b/beacon-chain/core/transition/BUILD.bazel index 1ae2f19e1b..a00b22aa08 100644 --- a/beacon-chain/core/transition/BUILD.bazel +++ b/beacon-chain/core/transition/BUILD.bazel @@ -25,6 +25,7 @@ go_library( "//beacon-chain/cache:go_default_library", "//beacon-chain/core/altair:go_default_library", "//beacon-chain/core/blocks:go_default_library", + "//beacon-chain/core/capella:go_default_library", "//beacon-chain/core/epoch:go_default_library", "//beacon-chain/core/epoch/precompute:go_default_library", "//beacon-chain/core/execution:go_default_library", diff --git a/beacon-chain/core/transition/transition.go b/beacon-chain/core/transition/transition.go index e4c233394e..a71b3ca431 100644 --- a/beacon-chain/core/transition/transition.go +++ b/beacon-chain/core/transition/transition.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v3/beacon-chain/cache" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/altair" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/capella" e "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/epoch" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/epoch/precompute" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/execution" @@ -255,7 +256,7 @@ func ProcessSlots(ctx context.Context, state state.BeaconState, slot types.Slot) tracing.AnnotateError(span, err) return nil, errors.Wrap(err, "could not process epoch with optimizations") } - case version.Altair, version.Bellatrix: + case version.Altair, version.Bellatrix, version.Capella: state, err = altair.ProcessEpoch(ctx, state) if err != nil { tracing.AnnotateError(span, err) @@ -285,6 +286,14 @@ func ProcessSlots(ctx context.Context, state state.BeaconState, slot types.Slot) return nil, err } } + + if time.CanUpgradeToCapella(state.Slot()) { + state, err = capella.UpgradeToCapella(state) + if err != nil { + tracing.AnnotateError(span, err) + return nil, err + } + } } if highestSlot < state.Slot() { diff --git a/beacon-chain/state/state-native/getters_payload_header.go b/beacon-chain/state/state-native/getters_payload_header.go index 5ac3492f04..6cbc582817 100644 --- a/beacon-chain/state/state-native/getters_payload_header.go +++ b/beacon-chain/state/state-native/getters_payload_header.go @@ -14,10 +14,6 @@ func (b *BeaconState) LatestExecutionPayloadHeader() (interfaces.ExecutionData, return nil, errNotSupported("LatestExecutionPayloadHeader", b.version) } - if b.latestExecutionPayloadHeader == nil { - return nil, nil - } - b.lock.RLock() defer b.lock.RUnlock() diff --git a/consensus-types/blocks/execution.go b/consensus-types/blocks/execution.go index aa51425266..1a2731c756 100644 --- a/consensus-types/blocks/execution.go +++ b/consensus-types/blocks/execution.go @@ -140,11 +140,21 @@ func (e executionPayload) Transactions() ([][]byte, error) { return e.p.Transactions, nil } +// TransactionsRoot -- +func (e executionPayload) TransactionsRoot() ([]byte, error) { + return nil, ErrUnsupportedGetter +} + // Withdrawals -- func (e executionPayload) Withdrawals() ([]*enginev1.Withdrawal, error) { return nil, ErrUnsupportedGetter } +// WithdrawalsRoot -- +func (e executionPayload) WithdrawalsRoot() ([]byte, error) { + return nil, ErrUnsupportedGetter +} + // executionPayloadHeader is a convenience wrapper around a blinded beacon block body's execution header data structure // This wrapper allows us to conform to a common interface so that beacon // blocks for future forks can also be applied across Prysm without issues. @@ -271,11 +281,21 @@ func (executionPayloadHeader) Transactions() ([][]byte, error) { return nil, ErrUnsupportedGetter } +// TransactionsRoot -- +func (e executionPayloadHeader) TransactionsRoot() ([]byte, error) { + return e.p.TransactionsRoot, nil +} + // Withdrawals -- func (e executionPayloadHeader) Withdrawals() ([]*enginev1.Withdrawal, error) { return nil, ErrUnsupportedGetter } +// WithdrawalsRoot -- +func (e executionPayloadHeader) WithdrawalsRoot() ([]byte, error) { + return nil, ErrUnsupportedGetter +} + // PayloadToHeader converts `payload` into execution payload header format. func PayloadToHeader(payload interfaces.ExecutionData) (*enginev1.ExecutionPayloadHeader, error) { txs, err := payload.Transactions() @@ -430,11 +450,21 @@ func (e executionPayloadCapella) Transactions() ([][]byte, error) { return e.p.Transactions, nil } +// TransactionsRoot -- +func (e executionPayloadCapella) TransactionsRoot() ([]byte, error) { + return nil, ErrUnsupportedGetter +} + // Withdrawals -- func (e executionPayloadCapella) Withdrawals() ([]*enginev1.Withdrawal, error) { return e.p.Withdrawals, nil } +// WithdrawalsRoot -- +func (e executionPayloadCapella) WithdrawalsRoot() ([]byte, error) { + return nil, ErrUnsupportedGetter +} + // executionPayloadHeaderCapella is a convenience wrapper around a blinded beacon block body's execution header data structure // This wrapper allows us to conform to a common interface so that beacon // blocks for future forks can also be applied across Prysm without issues. @@ -561,11 +591,21 @@ func (executionPayloadHeaderCapella) Transactions() ([][]byte, error) { return nil, ErrUnsupportedGetter } +// TransactionsRoot -- +func (e executionPayloadHeaderCapella) TransactionsRoot() ([]byte, error) { + return e.p.TransactionsRoot, nil +} + // Withdrawals -- func (e executionPayloadHeaderCapella) Withdrawals() ([]*enginev1.Withdrawal, error) { return nil, ErrUnsupportedGetter } +// WitdrawalsRoot -- +func (e executionPayloadHeaderCapella) WithdrawalsRoot() ([]byte, error) { + return e.p.WithdrawalsRoot, nil +} + // PayloadToHeaderCapella converts `payload` into execution payload header format. func PayloadToHeaderCapella(payload interfaces.ExecutionData) (*enginev1.ExecutionPayloadHeaderCapella, error) { txs, err := payload.Transactions() diff --git a/consensus-types/blocks/execution_test.go b/consensus-types/blocks/execution_test.go index 98962d53a7..0e1e6cf7d5 100644 --- a/consensus-types/blocks/execution_test.go +++ b/consensus-types/blocks/execution_test.go @@ -132,6 +132,14 @@ func TestWrapExecutionPayloadHeaderCapella(t *testing.T) { require.NoError(t, err) assert.DeepEqual(t, data, payload.Proto()) + + txRoot, err := payload.TransactionsRoot() + require.NoError(t, err) + require.DeepEqual(t, txRoot, data.TransactionsRoot) + + wrRoot, err := payload.WithdrawalsRoot() + require.NoError(t, err) + require.DeepEqual(t, wrRoot, data.WithdrawalsRoot) } func TestWrapExecutionPayloadCapella_IsNil(t *testing.T) { diff --git a/consensus-types/interfaces/beacon_block.go b/consensus-types/interfaces/beacon_block.go index 9f62d8b669..85d934fd0f 100644 --- a/consensus-types/interfaces/beacon_block.go +++ b/consensus-types/interfaces/beacon_block.go @@ -93,5 +93,7 @@ type ExecutionData interface { BaseFeePerGas() []byte BlockHash() []byte Transactions() ([][]byte, error) + TransactionsRoot() ([]byte, error) Withdrawals() ([]*enginev1.Withdrawal, error) + WithdrawalsRoot() ([]byte, error) } diff --git a/consensus-types/mock/block.go b/consensus-types/mock/block.go index edd215d132..2761ac2237 100644 --- a/consensus-types/mock/block.go +++ b/consensus-types/mock/block.go @@ -223,6 +223,10 @@ func (BeaconBlockBody) BLSToExecutionChanges() ([]*eth.SignedBLSToExecutionChang panic("implement me") } +func (b *BeaconBlock) SetStateRoot(root []byte) { + panic("implement me") +} + var _ interfaces.SignedBeaconBlock = &SignedBeaconBlock{} var _ interfaces.BeaconBlock = &BeaconBlock{} var _ interfaces.BeaconBlockBody = &BeaconBlockBody{}