diff --git a/beacon-chain/rpc/eth/beacon/config_test.go b/beacon-chain/rpc/eth/beacon/config_test.go index 75b616d04609..33694154593e 100644 --- a/beacon-chain/rpc/eth/beacon/config_test.go +++ b/beacon-chain/rpc/eth/beacon/config_test.go @@ -51,6 +51,8 @@ func TestGetSpec(t *testing.T) { config.BellatrixForkEpoch = 101 config.CapellaForkVersion = []byte("CapellaForkVersion") config.CapellaForkEpoch = 103 + config.DenebForkVersion = []byte("DenebForkVersion") + config.DenebForkEpoch = 105 config.BLSWithdrawalPrefixByte = byte('b') config.ETH1AddressWithdrawalPrefixByte = byte('c') config.GenesisDelay = 24 @@ -136,7 +138,7 @@ func TestGetSpec(t *testing.T) { resp, err := server.GetSpec(context.Background(), &emptypb.Empty{}) require.NoError(t, err) - assert.Equal(t, 105, len(resp.Data)) + assert.Equal(t, 107, len(resp.Data)) for k, v := range resp.Data { switch k { case "CONFIG_NAME": @@ -205,6 +207,10 @@ func TestGetSpec(t *testing.T) { assert.Equal(t, "0x"+hex.EncodeToString([]byte("CapellaForkVersion")), v) case "CAPELLA_FORK_EPOCH": assert.Equal(t, "103", v) + case "DENEB_FORK_VERSION": + assert.Equal(t, "0x"+hex.EncodeToString([]byte("DenebForkVersion")), v) + case "DENEB_FORK_EPOCH": + assert.Equal(t, "105", v) case "MIN_ANCHOR_POW_BLOCK_DIFFICULTY": assert.Equal(t, "1000", v) case "BLS_WITHDRAWAL_PREFIX": diff --git a/beacon-chain/state/state-native/beacon_state_mainnet.go b/beacon-chain/state/state-native/beacon_state_mainnet.go index 5b2371948d27..47269573aacc 100644 --- a/beacon-chain/state/state-native/beacon_state_mainnet.go +++ b/beacon-chain/state/state-native/beacon_state_mainnet.go @@ -48,6 +48,7 @@ type BeaconState struct { nextSyncCommittee *ethpb.SyncCommittee latestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader latestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella + latestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb nextWithdrawalIndex uint64 nextWithdrawalValidatorIndex primitives.ValidatorIndex diff --git a/beacon-chain/state/state-native/beacon_state_minimal.go b/beacon-chain/state/state-native/beacon_state_minimal.go index b5ac9331b1c9..dae615c127a9 100644 --- a/beacon-chain/state/state-native/beacon_state_minimal.go +++ b/beacon-chain/state/state-native/beacon_state_minimal.go @@ -48,6 +48,7 @@ type BeaconState struct { nextSyncCommittee *ethpb.SyncCommittee latestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader latestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella + latestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb nextWithdrawalIndex uint64 nextWithdrawalValidatorIndex primitives.ValidatorIndex diff --git a/beacon-chain/state/state-native/getters_block_test.go b/beacon-chain/state/state-native/getters_block_test.go index 4a20e4848a21..7a1cacb05be5 100644 --- a/beacon-chain/state/state-native/getters_block_test.go +++ b/beacon-chain/state/state-native/getters_block_test.go @@ -56,6 +56,18 @@ func TestBeaconState_LatestBlockHeader_Capella(t *testing.T) { ) } +func TestBeaconState_LatestBlockHeader_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateLatestBlockHeader( + t, + func() (state.BeaconState, error) { + return InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{}) + }, + func(BH *ethpb.BeaconBlockHeader) (state.BeaconState, error) { + return InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{LatestBlockHeader: BH}) + }, + ) +} + func TestBeaconState_BlockRoots_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateBlockRootsNative( t, @@ -104,6 +116,18 @@ func TestBeaconState_BlockRoots_Capella(t *testing.T) { ) } +func TestBeaconState_BlockRoots_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateBlockRootsNative( + t, + func() (state.BeaconState, error) { + return InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{}) + }, + func(BR [][]byte) (state.BeaconState, error) { + return InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{BlockRoots: BR}) + }, + ) +} + func TestBeaconState_BlockRootAtIndex_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateBlockRootAtIndexNative( t, @@ -151,3 +175,15 @@ func TestBeaconState_BlockRootAtIndex_Capella(t *testing.T) { }, ) } + +func TestBeaconState_BlockRootAtIndex_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateBlockRootAtIndexNative( + t, + func() (state.BeaconState, error) { + return InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{}) + }, + func(BR [][]byte) (state.BeaconState, error) { + return InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{BlockRoots: BR}) + }, + ) +} diff --git a/beacon-chain/state/state-native/getters_checkpoint_test.go b/beacon-chain/state/state-native/getters_checkpoint_test.go index f835ec0cfac8..b268341648cd 100644 --- a/beacon-chain/state/state-native/getters_checkpoint_test.go +++ b/beacon-chain/state/state-native/getters_checkpoint_test.go @@ -41,6 +41,14 @@ func TestBeaconState_PreviousJustifiedCheckpointNil_Capella(t *testing.T) { }) } +func TestBeaconState_PreviousJustifiedCheckpointNil_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStatePreviousJustifiedCheckpointNil( + t, + func() (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{}) + }) +} + func TestBeaconState_PreviousJustifiedCheckpoint_Phase0(t *testing.T) { testtmpl.VerifyBeaconStatePreviousJustifiedCheckpoint( t, @@ -73,6 +81,14 @@ func TestBeaconState_PreviousJustifiedCheckpoint_Capella(t *testing.T) { }) } +func TestBeaconState_PreviousJustifiedCheckpoint_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStatePreviousJustifiedCheckpoint( + t, + func(cp *ethpb.Checkpoint) (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{PreviousJustifiedCheckpoint: cp}) + }) +} + func TestBeaconState_CurrentJustifiedCheckpointNil_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateCurrentJustifiedCheckpointNil( t, @@ -105,6 +121,14 @@ func TestBeaconState_CurrentJustifiedCheckpointNil_Capella(t *testing.T) { }) } +func TestBeaconState_CurrentJustifiedCheckpointNil_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateCurrentJustifiedCheckpointNil( + t, + func() (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{}) + }) +} + func TestBeaconState_CurrentJustifiedCheckpoint_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateCurrentJustifiedCheckpoint( t, @@ -137,6 +161,14 @@ func TestBeaconState_CurrentJustifiedCheckpoint_Capella(t *testing.T) { }) } +func TestBeaconState_CurrentJustifiedCheckpoint_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateCurrentJustifiedCheckpoint( + t, + func(cp *ethpb.Checkpoint) (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{CurrentJustifiedCheckpoint: cp}) + }) +} + func TestBeaconState_FinalizedCheckpointNil_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateFinalizedCheckpointNil( t, @@ -169,6 +201,14 @@ func TestBeaconState_FinalizedCheckpointNil_Capella(t *testing.T) { }) } +func TestBeaconState_FinalizedCheckpointNil_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateFinalizedCheckpointNil( + t, + func() (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{}) + }) +} + func TestBeaconState_FinalizedCheckpoint_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateFinalizedCheckpoint( t, @@ -201,6 +241,14 @@ func TestBeaconState_FinalizedCheckpoint_Capella(t *testing.T) { }) } +func TestBeaconState_FinalizedCheckpoint_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateFinalizedCheckpoint( + t, + func(cp *ethpb.Checkpoint) (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{FinalizedCheckpoint: cp}) + }) +} + func TestBeaconState_JustificationBitsNil_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateJustificationBitsNil( t, @@ -233,6 +281,14 @@ func TestBeaconState_JustificationBitsNil_Capella(t *testing.T) { }) } +func TestBeaconState_JustificationBitsNil_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateJustificationBitsNil( + t, + func() (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{}) + }) +} + func TestBeaconState_JustificationBits_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateJustificationBits( t, @@ -264,3 +320,11 @@ func TestBeaconState_JustificationBits_Capella(t *testing.T) { return InitializeFromProtoUnsafeCapella(ðpb.BeaconStateCapella{JustificationBits: bits}) }) } + +func TestBeaconState_JustificationBits_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateJustificationBits( + t, + func(bits bitfield.Bitvector4) (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{JustificationBits: bits}) + }) +} diff --git a/beacon-chain/state/state-native/getters_payload_header.go b/beacon-chain/state/state-native/getters_payload_header.go index b5dc09bc4915..214d815069e6 100644 --- a/beacon-chain/state/state-native/getters_payload_header.go +++ b/beacon-chain/state/state-native/getters_payload_header.go @@ -20,7 +20,12 @@ func (b *BeaconState) LatestExecutionPayloadHeader() (interfaces.ExecutionData, if b.version == version.Bellatrix { return blocks.WrappedExecutionPayloadHeader(b.latestExecutionPayloadHeaderVal()) } - return blocks.WrappedExecutionPayloadHeaderCapella(b.latestExecutionPayloadHeaderCapellaVal(), 0) + + if b.version == version.Capella { + return blocks.WrappedExecutionPayloadHeaderCapella(b.latestExecutionPayloadHeaderCapellaVal(), 0) + } + + return blocks.WrappedExecutionPayloadHeaderDeneb(b.latestExecutionPayloadHeaderDenebVal(), 0) } // latestExecutionPayloadHeaderVal of the beacon state. @@ -34,3 +39,7 @@ func (b *BeaconState) latestExecutionPayloadHeaderVal() *enginev1.ExecutionPaylo func (b *BeaconState) latestExecutionPayloadHeaderCapellaVal() *enginev1.ExecutionPayloadHeaderCapella { return ethpb.CopyExecutionPayloadHeaderCapella(b.latestExecutionPayloadHeaderCapella) } + +func (b *BeaconState) latestExecutionPayloadHeaderDenebVal() *enginev1.ExecutionPayloadHeaderDeneb { + return ethpb.CopyExecutionPayloadHeaderDeneb(b.latestExecutionPayloadHeaderDeneb) +} diff --git a/beacon-chain/state/state-native/getters_state.go b/beacon-chain/state/state-native/getters_state.go index 63b5c0dc8931..7e0019e656b9 100644 --- a/beacon-chain/state/state-native/getters_state.go +++ b/beacon-chain/state/state-native/getters_state.go @@ -128,6 +128,37 @@ func (b *BeaconState) ToProtoUnsafe() interface{} { NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, HistoricalSummaries: b.historicalSummaries, } + case version.Deneb: + return ðpb.BeaconStateDeneb{ + GenesisTime: b.genesisTime, + GenesisValidatorsRoot: gvrCopy[:], + Slot: b.slot, + Fork: b.fork, + LatestBlockHeader: b.latestBlockHeader, + BlockRoots: b.blockRoots.Slice(), + StateRoots: b.stateRoots.Slice(), + HistoricalRoots: b.historicalRoots.Slice(), + Eth1Data: b.eth1Data, + Eth1DataVotes: b.eth1DataVotes, + Eth1DepositIndex: b.eth1DepositIndex, + Validators: b.validators, + Balances: b.balances, + RandaoMixes: b.randaoMixes.Slice(), + Slashings: b.slashings, + PreviousEpochParticipation: b.previousEpochParticipation, + CurrentEpochParticipation: b.currentEpochParticipation, + JustificationBits: b.justificationBits, + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint, + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint, + FinalizedCheckpoint: b.finalizedCheckpoint, + InactivityScores: b.inactivityScores, + CurrentSyncCommittee: b.currentSyncCommittee, + NextSyncCommittee: b.nextSyncCommittee, + LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderDeneb, + NextWithdrawalIndex: b.nextWithdrawalIndex, + NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + HistoricalSummaries: b.historicalSummaries, + } default: return nil } @@ -255,6 +286,37 @@ func (b *BeaconState) ToProto() interface{} { NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, HistoricalSummaries: b.historicalSummariesVal(), } + case version.Deneb: + return ðpb.BeaconStateDeneb{ + GenesisTime: b.genesisTime, + GenesisValidatorsRoot: gvrCopy[:], + Slot: b.slot, + Fork: b.forkVal(), + LatestBlockHeader: b.latestBlockHeaderVal(), + BlockRoots: b.blockRoots.Slice(), + StateRoots: b.stateRoots.Slice(), + HistoricalRoots: b.historicalRoots.Slice(), + Eth1Data: b.eth1DataVal(), + Eth1DataVotes: b.eth1DataVotesVal(), + Eth1DepositIndex: b.eth1DepositIndex, + Validators: b.validatorsVal(), + Balances: b.balancesVal(), + RandaoMixes: b.randaoMixes.Slice(), + Slashings: b.slashingsVal(), + PreviousEpochParticipation: b.previousEpochParticipationVal(), + CurrentEpochParticipation: b.currentEpochParticipationVal(), + JustificationBits: b.justificationBitsVal(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpointVal(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpointVal(), + FinalizedCheckpoint: b.finalizedCheckpointVal(), + InactivityScores: b.inactivityScoresVal(), + CurrentSyncCommittee: b.currentSyncCommitteeVal(), + NextSyncCommittee: b.nextSyncCommitteeVal(), + LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderDenebVal(), + NextWithdrawalIndex: b.nextWithdrawalIndex, + NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + HistoricalSummaries: b.historicalSummariesVal(), + } default: return nil } @@ -338,3 +400,13 @@ func ProtobufBeaconStateCapella(s interface{}) (*ethpb.BeaconStateCapella, error } return pbState, nil } + +// ProtobufBeaconStateDeneb transforms an input into beacon state Deneb in the form of protobuf. +// Error is returned if the input is not type protobuf beacon state. +func ProtobufBeaconStateDeneb(s interface{}) (*ethpb.BeaconStateDeneb, error) { + pbState, ok := s.(*ethpb.BeaconStateDeneb) + if !ok { + return nil, errors.New("input is not type pb.ProtobufBeaconStateDeneb") + } + return pbState, nil +} diff --git a/beacon-chain/state/state-native/getters_test.go b/beacon-chain/state/state-native/getters_test.go index 600606cb24f3..53e36d7c936b 100644 --- a/beacon-chain/state/state-native/getters_test.go +++ b/beacon-chain/state/state-native/getters_test.go @@ -32,6 +32,12 @@ func TestBeaconState_SlotDataRace_Capella(t *testing.T) { }) } +func TestBeaconState_SlotDataRace_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateSlotDataRace(t, func() (state.BeaconState, error) { + return InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{Slot: 1}) + }) +} + func TestBeaconState_MatchCurrentJustifiedCheckpt_Phase0(t *testing.T) { testtmpl.VerifyBeaconStateMatchCurrentJustifiedCheckptNative( t, diff --git a/beacon-chain/state/state-native/getters_validator_test.go b/beacon-chain/state/state-native/getters_validator_test.go index 4d09835e69a8..c566bc08eb12 100644 --- a/beacon-chain/state/state-native/getters_validator_test.go +++ b/beacon-chain/state/state-native/getters_validator_test.go @@ -44,6 +44,14 @@ func TestBeaconState_ValidatorAtIndexReadOnly_HandlesNilSlice_Capella(t *testing }) } +func TestBeaconState_ValidatorAtIndexReadOnly_HandlesNilSlice_Deneb(t *testing.T) { + testtmpl.VerifyBeaconStateValidatorAtIndexReadOnlyHandlesNilSlice(t, func() (state.BeaconState, error) { + return statenative.InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{ + Validators: nil, + }) + }) +} + func TestValidatorIndexOutOfRangeError(t *testing.T) { err := statenative.NewValidatorIndexOutOfRangeError(1) require.Equal(t, err.Error(), "index 1 out of range") diff --git a/beacon-chain/state/state-native/getters_withdrawal_test.go b/beacon-chain/state/state-native/getters_withdrawal_test.go index e23b9ad3a3cc..867f1690e758 100644 --- a/beacon-chain/state/state-native/getters_withdrawal_test.go +++ b/beacon-chain/state/state-native/getters_withdrawal_test.go @@ -13,6 +13,12 @@ import ( ) func TestNextWithdrawalIndex(t *testing.T) { + t.Run("ok for deneb", func(t *testing.T) { + s := BeaconState{version: version.Deneb, nextWithdrawalIndex: 123} + i, err := s.NextWithdrawalIndex() + require.NoError(t, err) + assert.Equal(t, uint64(123), i) + }) t.Run("ok", func(t *testing.T) { s := BeaconState{version: version.Capella, nextWithdrawalIndex: 123} i, err := s.NextWithdrawalIndex() @@ -27,6 +33,12 @@ func TestNextWithdrawalIndex(t *testing.T) { } func TestNextWithdrawalValidatorIndex(t *testing.T) { + t.Run("ok for deneb", func(t *testing.T) { + s := BeaconState{version: version.Deneb, nextWithdrawalValidatorIndex: 123} + i, err := s.NextWithdrawalValidatorIndex() + require.NoError(t, err) + assert.Equal(t, primitives.ValidatorIndex(123), i) + }) t.Run("ok", func(t *testing.T) { s := BeaconState{version: version.Capella, nextWithdrawalValidatorIndex: 123} i, err := s.NextWithdrawalValidatorIndex() @@ -329,3 +341,257 @@ func TestExpectedWithdrawals(t *testing.T) { params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved }) } + +func TestExpectedWithdrawals_Deneb(t *testing.T) { + t.Run("no withdrawals", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 0, len(expected)) + }) + t.Run("one fully withdrawable", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + nextWithdrawalValidatorIndex: 20, + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + s.validators[3].WithdrawableEpoch = primitives.Epoch(0) + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 1, len(expected)) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: s.validators[3].WithdrawalCredentials[12:], + Amount: s.balances[3], + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("one partially withdrawable", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + s.balances[3] += params.BeaconConfig().MinDepositAmount + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 1, len(expected)) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: s.validators[3].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MinDepositAmount, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("one partially and one fully withdrawable", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + val.WithdrawalCredentials[31] = byte(i) + s.validators[i] = val + } + s.balances[3] += params.BeaconConfig().MinDepositAmount + s.validators[7].WithdrawableEpoch = primitives.Epoch(0) + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 2, len(expected)) + + withdrawalFull := &enginev1.Withdrawal{ + Index: 1, + ValidatorIndex: 7, + Address: s.validators[7].WithdrawalCredentials[12:], + Amount: s.balances[7], + } + withdrawalPartial := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: s.validators[3].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MinDepositAmount, + } + require.DeepEqual(t, withdrawalPartial, expected[0]) + require.DeepEqual(t, withdrawalFull, expected[1]) + }) + t.Run("all partially withdrawable", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 0, + Address: s.validators[0].WithdrawalCredentials[12:], + Amount: 1, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("all fully withdrawable", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(0), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 0, + Address: s.validators[0].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MaxEffectiveBalance, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("all fully and partially withdrawable", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(0), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 0, + Address: s.validators[0].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MaxEffectiveBalance + 1, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("one fully withdrawable but zero balance", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + nextWithdrawalValidatorIndex: 20, + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + s.validators[3].WithdrawableEpoch = primitives.Epoch(0) + s.balances[3] = 0 + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 0, len(expected)) + }) + t.Run("one partially withdrawable, one above sweep bound", func(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + validators: make([]*ethpb.Validator, 100), + balances: make([]uint64, 100), + } + for i := range s.validators { + s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + s.validators[i] = val + } + s.balances[3] += params.BeaconConfig().MinDepositAmount + s.balances[10] += params.BeaconConfig().MinDepositAmount + saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep + params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = 10 + expected, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 1, len(expected)) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: s.validators[3].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MinDepositAmount, + } + require.DeepEqual(t, withdrawal, expected[0]) + params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved + }) +} diff --git a/beacon-chain/state/state-native/hasher.go b/beacon-chain/state/state-native/hasher.go index ec47e1c5a22a..f340032bf404 100644 --- a/beacon-chain/state/state-native/hasher.go +++ b/beacon-chain/state/state-native/hasher.go @@ -33,6 +33,8 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateBellatrixFieldCount) case version.Capella: fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateCapellaFieldCount) + case version.Deneb: + fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateCapellaFieldCount) } // Genesis time root. @@ -243,7 +245,18 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b return nil, err } fieldRoots[types.LatestExecutionPayloadHeaderCapella.RealPosition()] = executionPayloadRoot[:] + } + + if state.version == version.Deneb { + // Execution payload root. + executionPayloadRoot, err := state.latestExecutionPayloadHeaderDeneb.HashTreeRoot() + if err != nil { + return nil, err + } + fieldRoots[types.LatestExecutionPayloadHeaderDeneb.RealPosition()] = executionPayloadRoot[:] + } + if state.version >= version.Capella { // Next withdrawal index root. nextWithdrawalIndexRoot := make([]byte, 32) binary.LittleEndian.PutUint64(nextWithdrawalIndexRoot, state.nextWithdrawalIndex) diff --git a/beacon-chain/state/state-native/references_test.go b/beacon-chain/state/state-native/references_test.go index 5d90da6ba19b..a3c61485ad7f 100644 --- a/beacon-chain/state/state-native/references_test.go +++ b/beacon-chain/state/state-native/references_test.go @@ -131,6 +131,35 @@ func TestStateReferenceSharing_Finalizer_Capella(t *testing.T) { } } +func TestStateReferenceSharing_Finalizer_Deneb(t *testing.T) { + // This test showcases the logic on the RandaoMixes field with the GC finalizer. + + s, err := InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{RandaoMixes: [][]byte{[]byte("foo")}}) + require.NoError(t, err) + a, ok := s.(*BeaconState) + require.Equal(t, true, ok) + assert.Equal(t, uint(1), a.sharedFieldReferences[types.RandaoMixes].Refs(), "Expected a single reference for RANDAO mixes") + + func() { + // Create object in a different scope for GC + b := a.Copy() + assert.Equal(t, uint(2), a.sharedFieldReferences[types.RandaoMixes].Refs(), "Expected 2 references to RANDAO mixes") + _ = b + }() + + runtime.GC() // Should run finalizer on object b + assert.Equal(t, uint(1), a.sharedFieldReferences[types.RandaoMixes].Refs(), "Expected 1 shared reference to RANDAO mixes!") + + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + assert.Equal(t, uint(2), b.sharedFieldReferences[types.RandaoMixes].Refs(), "Expected 2 shared references to RANDAO mixes") + require.NoError(t, b.UpdateRandaoMixesAtIndex(0, []byte("bar"))) + if b.sharedFieldReferences[types.RandaoMixes].Refs() != 1 || a.sharedFieldReferences[types.RandaoMixes].Refs() != 1 { + t.Error("Expected 1 shared reference to RANDAO mix for both a and b") + } +} + func TestStateReferenceCopy_NoUnexpectedRootsMutation_Phase0(t *testing.T) { root1, root2 := bytesutil.ToBytes32([]byte("foo")), bytesutil.ToBytes32([]byte("bar")) s, err := InitializeFromProtoUnsafePhase0(ðpb.BeaconState{ @@ -367,6 +396,65 @@ func TestStateReferenceCopy_NoUnexpectedRootsMutation_Capella(t *testing.T) { assertRefCount(t, b, types.StateRoots, 1) } +func TestStateReferenceCopy_NoUnexpectedRootsMutation_Deneb(t *testing.T) { + root1, root2 := bytesutil.ToBytes32([]byte("foo")), bytesutil.ToBytes32([]byte("bar")) + s, err := InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{ + BlockRoots: [][]byte{ + root1[:], + }, + StateRoots: [][]byte{ + root1[:], + }, + }) + require.NoError(t, err) + a, ok := s.(*BeaconState) + require.Equal(t, true, ok) + require.NoError(t, err) + assertRefCount(t, a, types.BlockRoots, 1) + assertRefCount(t, a, types.StateRoots, 1) + + // Copy, increases reference count. + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + assertRefCount(t, a, types.BlockRoots, 2) + assertRefCount(t, a, types.StateRoots, 2) + assertRefCount(t, b, types.BlockRoots, 2) + assertRefCount(t, b, types.StateRoots, 2) + + // Assert shared state. + blockRootsA := a.BlockRoots() + stateRootsA := a.StateRoots() + blockRootsB := b.BlockRoots() + stateRootsB := b.StateRoots() + assertValFound(t, blockRootsA, root1[:]) + assertValFound(t, blockRootsB, root1[:]) + assertValFound(t, stateRootsA, root1[:]) + assertValFound(t, stateRootsB, root1[:]) + + // Mutator should only affect calling state: a. + require.NoError(t, a.UpdateBlockRootAtIndex(0, root2)) + require.NoError(t, a.UpdateStateRootAtIndex(0, root2)) + + // Assert no shared state mutation occurred only on state a (copy on write). + assertValNotFound(t, a.BlockRoots(), root1[:]) + assertValNotFound(t, a.StateRoots(), root1[:]) + assertValFound(t, a.BlockRoots(), root2[:]) + assertValFound(t, a.StateRoots(), root2[:]) + assertValFound(t, b.BlockRoots(), root1[:]) + assertValFound(t, b.StateRoots(), root1[:]) + assert.DeepEqual(t, root2[:], a.BlockRoots()[0], "Expected mutation not found") + assert.DeepEqual(t, root2[:], a.StateRoots()[0], "Expected mutation not found") + assert.DeepEqual(t, root1[:], blockRootsB[0], "Unexpected mutation found") + assert.DeepEqual(t, root1[:], stateRootsB[0], "Unexpected mutation found") + + // Copy on write happened, reference counters are reset. + assertRefCount(t, a, types.BlockRoots, 1) + assertRefCount(t, a, types.StateRoots, 1) + assertRefCount(t, b, types.BlockRoots, 1) + assertRefCount(t, b, types.StateRoots, 1) +} + func TestStateReferenceCopy_NoUnexpectedRandaoMutation_Phase0(t *testing.T) { val1, val2 := bytesutil.ToBytes32([]byte("foo")), bytesutil.ToBytes32([]byte("bar")) s, err := InitializeFromProtoUnsafePhase0(ðpb.BeaconState{ @@ -543,6 +631,50 @@ func TestStateReferenceCopy_NoUnexpectedRandaoMutation_Capella(t *testing.T) { assertRefCount(t, b, types.RandaoMixes, 1) } +func TestStateReferenceCopy_NoUnexpectedRandaoMutation_Deneb(t *testing.T) { + val1, val2 := bytesutil.ToBytes32([]byte("foo")), bytesutil.ToBytes32([]byte("bar")) + s, err := InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{ + RandaoMixes: [][]byte{ + val1[:], + }, + }) + require.NoError(t, err) + a, ok := s.(*BeaconState) + require.Equal(t, true, ok) + require.NoError(t, err) + assertRefCount(t, a, types.RandaoMixes, 1) + + // Copy, increases reference count. + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + assertRefCount(t, a, types.RandaoMixes, 2) + assertRefCount(t, b, types.RandaoMixes, 2) + + // Assert shared state. + mixesA := a.RandaoMixes() + mixesB := b.RandaoMixes() + assertValFound(t, mixesA, val1[:]) + assertValFound(t, mixesB, val1[:]) + + // Mutator should only affect calling state: a. + require.NoError(t, a.UpdateRandaoMixesAtIndex(0, val2[:])) + + // Assert no shared state mutation occurred only on state a (copy on write). + assertValFound(t, a.RandaoMixes(), val2[:]) + assertValNotFound(t, a.RandaoMixes(), val1[:]) + assertValFound(t, b.RandaoMixes(), val1[:]) + assertValNotFound(t, b.RandaoMixes(), val2[:]) + assertValFound(t, mixesB, val1[:]) + assertValNotFound(t, mixesB, val2[:]) + assert.DeepEqual(t, val2[:], a.RandaoMixes()[0], "Expected mutation not found") + assert.DeepEqual(t, val1[:], mixesB[0], "Unexpected mutation found") + + // Copy on write happened, reference counters are reset. + assertRefCount(t, a, types.RandaoMixes, 1) + assertRefCount(t, b, types.RandaoMixes, 1) +} + func TestStateReferenceCopy_NoUnexpectedAttestationsMutation(t *testing.T) { assertAttFound := func(vals []*ethpb.PendingAttestation, val uint64) { for i := range vals { @@ -813,6 +945,41 @@ func TestValidatorReferences_RemainsConsistent_Capella(t *testing.T) { })) } +func TestValidatorReferences_RemainsConsistent_Deneb(t *testing.T) { + s, err := InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{ + Validators: []*ethpb.Validator{ + {PublicKey: []byte{'A'}}, + {PublicKey: []byte{'B'}}, + {PublicKey: []byte{'C'}}, + {PublicKey: []byte{'D'}}, + {PublicKey: []byte{'E'}}, + }, + }) + require.NoError(t, err) + a, ok := s.(*BeaconState) + require.Equal(t, true, ok) + + // Create a second state. + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + + // Update First Validator. + assert.NoError(t, a.UpdateValidatorAtIndex(0, ðpb.Validator{PublicKey: []byte{'Z'}})) + + assert.DeepNotEqual(t, a.Validators()[0], b.Validators()[0], "validators are equal when they are supposed to be different") + // Modify all validators from copied state. + assert.NoError(t, b.ApplyToEveryValidator(func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) { + return true, ðpb.Validator{PublicKey: []byte{'V'}}, nil + })) + + // Ensure reference is properly accounted for. + assert.NoError(t, a.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error { + assert.NotEqual(t, bytesutil.ToBytes48([]byte{'V'}), val.PublicKey()) + return nil + })) +} + func TestValidatorReferences_RemainsConsistent_Bellatrix(t *testing.T) { s, err := InitializeFromProtoUnsafeBellatrix(ðpb.BeaconStateBellatrix{ Validators: []*ethpb.Validator{ diff --git a/beacon-chain/state/state-native/setters_payload_header.go b/beacon-chain/state/state-native/setters_payload_header.go index 2c355c798862..feaeded43ace 100644 --- a/beacon-chain/state/state-native/setters_payload_header.go +++ b/beacon-chain/state/state-native/setters_payload_header.go @@ -36,6 +36,14 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa b.latestExecutionPayloadHeaderCapella = latest b.markFieldAsDirty(types.LatestExecutionPayloadHeaderCapella) return nil + case *enginev1.ExecutionPayloadDeneb: + latest, err := consensusblocks.PayloadToHeaderDeneb(val) + if err != nil { + return errors.Wrap(err, "could not convert payload to header") + } + b.latestExecutionPayloadHeaderDeneb = latest + b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb) + return nil case *enginev1.ExecutionPayloadHeader: b.latestExecutionPayloadHeader = header b.markFieldAsDirty(types.LatestExecutionPayloadHeader) @@ -44,6 +52,10 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa b.latestExecutionPayloadHeaderCapella = header b.markFieldAsDirty(types.LatestExecutionPayloadHeaderCapella) return nil + case *enginev1.ExecutionPayloadHeaderDeneb: + b.latestExecutionPayloadHeaderDeneb = header + b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb) + return nil default: return errors.New("value must be an execution payload header") } diff --git a/beacon-chain/state/state-native/setters_withdrawal_test.go b/beacon-chain/state/state-native/setters_withdrawal_test.go index 9035f47073e9..674539f69adb 100644 --- a/beacon-chain/state/state-native/setters_withdrawal_test.go +++ b/beacon-chain/state/state-native/setters_withdrawal_test.go @@ -30,3 +30,25 @@ func TestSetNextWithdrawalValidatorIndex(t *testing.T) { require.Equal(t, primitives.ValidatorIndex(5), s.nextWithdrawalValidatorIndex) require.Equal(t, true, s.dirtyFields[types.NextWithdrawalValidatorIndex]) } + +func TestSetNextWithdrawalIndex_Deneb(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + nextWithdrawalIndex: 3, + dirtyFields: make(map[types.FieldIndex]bool), + } + require.NoError(t, s.SetNextWithdrawalIndex(5)) + require.Equal(t, uint64(5), s.nextWithdrawalIndex) + require.Equal(t, true, s.dirtyFields[types.NextWithdrawalIndex]) +} + +func TestSetNextWithdrawalValidatorIndex_Deneb(t *testing.T) { + s := BeaconState{ + version: version.Deneb, + nextWithdrawalValidatorIndex: 3, + dirtyFields: make(map[types.FieldIndex]bool), + } + require.NoError(t, s.SetNextWithdrawalValidatorIndex(5)) + require.Equal(t, primitives.ValidatorIndex(5), s.nextWithdrawalValidatorIndex) + require.Equal(t, true, s.dirtyFields[types.NextWithdrawalValidatorIndex]) +} diff --git a/beacon-chain/state/state-native/spec_parameters.go b/beacon-chain/state/state-native/spec_parameters.go index 35c612d1e9da..b405be2162fe 100644 --- a/beacon-chain/state/state-native/spec_parameters.go +++ b/beacon-chain/state/state-native/spec_parameters.go @@ -7,7 +7,7 @@ import ( func (b *BeaconState) ProportionalSlashingMultiplier() (uint64, error) { switch b.version { - case version.Bellatrix, version.Capella: + case version.Bellatrix, version.Capella, version.Deneb: return params.BeaconConfig().ProportionalSlashingMultiplierBellatrix, nil case version.Altair: return params.BeaconConfig().ProportionalSlashingMultiplierAltair, nil @@ -19,7 +19,7 @@ func (b *BeaconState) ProportionalSlashingMultiplier() (uint64, error) { func (b *BeaconState) InactivityPenaltyQuotient() (uint64, error) { switch b.version { - case version.Bellatrix, version.Capella: + case version.Bellatrix, version.Capella, version.Deneb: return params.BeaconConfig().InactivityPenaltyQuotientBellatrix, nil case version.Altair: return params.BeaconConfig().InactivityPenaltyQuotientAltair, nil diff --git a/beacon-chain/state/state-native/state_fuzz_test.go b/beacon-chain/state/state-native/state_fuzz_test.go index 5e57273c1f24..ad396b47c5d8 100644 --- a/beacon-chain/state/state-native/state_fuzz_test.go +++ b/beacon-chain/state/state-native/state_fuzz_test.go @@ -267,7 +267,7 @@ func FuzzCapellaStateHashTreeRoot(f *testing.F) { assert.NoError(t, err) // Perform a cold HTR calculation by initializing a new state. innerState, ok := stateObj.ToProtoUnsafe().(*ethpb.BeaconStateCapella) - assert.Equal(t, true, ok, "inner state is a not a beacon state bellatrix proto") + assert.Equal(t, true, ok, "inner state is a not a beacon state capella proto") newState, err := native.InitializeFromProtoUnsafeCapella(innerState) assert.NoError(t, err) @@ -292,3 +292,73 @@ func FuzzCapellaStateHashTreeRoot(f *testing.F) { } }) } + +func FuzzDenebStateHashTreeRoot(f *testing.F) { + gState, _ := util.DeterministicGenesisStateDeneb(f, 100) + output, err := gState.MarshalSSZ() + assert.NoError(f, err) + randPool := make([]byte, 100) + _, err = rand.NewDeterministicGenerator().Read(randPool) + assert.NoError(f, err) + f.Add(randPool, uint64(10)) + f.Fuzz(func(t *testing.T, diffBuffer []byte, slotsToTransition uint64) { + stateSSZ := bytesutil.SafeCopyBytes(output) + for i := 0; i < len(diffBuffer); i += 9 { + if i+8 >= len(diffBuffer) { + return + } + num := bytesutil.BytesToUint64BigEndian(diffBuffer[i : i+8]) + num %= uint64(len(diffBuffer)) + // Perform a XOR on the byte of the selected index. + stateSSZ[num] ^= diffBuffer[i+8] + } + pbState := ðpb.BeaconStateDeneb{} + err := pbState.UnmarshalSSZ(stateSSZ) + if err != nil { + return + } + nativeState, err := native.InitializeFromProtoDeneb(pbState) + if err != nil { + return + } + + slotsToTransition %= 100 + stateObj, err := native.InitializeFromProtoUnsafeDeneb(pbState) + assert.NoError(t, err) + for stateObj.Slot() < primitives.Slot(slotsToTransition) { + stateObj, err = coreState.ProcessSlots(context.Background(), stateObj, stateObj.Slot()+1) + assert.NoError(t, err) + stateObj.Copy() + + nativeState, err = coreState.ProcessSlots(context.Background(), nativeState, nativeState.Slot()+1) + assert.NoError(t, err) + nativeState.Copy() + } + assert.NoError(t, err) + // Perform a cold HTR calculation by initializing a new state. + innerState, ok := stateObj.ToProtoUnsafe().(*ethpb.BeaconStateDeneb) + assert.Equal(t, true, ok, "inner state is a not a beacon state deneb proto") + newState, err := native.InitializeFromProtoUnsafeDeneb(innerState) + assert.NoError(t, err) + + newRt, newErr := newState.HashTreeRoot(context.Background()) + rt, err := stateObj.HashTreeRoot(context.Background()) + nativeRt, nativeErr := nativeState.HashTreeRoot(context.Background()) + assert.Equal(t, newErr != nil, err != nil) + assert.Equal(t, newErr != nil, nativeErr != nil) + if err == nil { + assert.Equal(t, rt, newRt) + assert.Equal(t, rt, nativeRt) + } + + newSSZ, newErr := newState.MarshalSSZ() + stateObjSSZ, err := stateObj.MarshalSSZ() + nativeSSZ, nativeErr := nativeState.MarshalSSZ() + assert.Equal(t, newErr != nil, err != nil) + assert.Equal(t, newErr != nil, nativeErr != nil) + if err == nil { + assert.DeepEqual(t, newSSZ, stateObjSSZ) + assert.DeepEqual(t, newSSZ, nativeSSZ) + } + }) +} diff --git a/beacon-chain/state/state-native/state_test.go b/beacon-chain/state/state-native/state_test.go index 3542f4c880b9..7547fab4e5c5 100644 --- a/beacon-chain/state/state-native/state_test.go +++ b/beacon-chain/state/state-native/state_test.go @@ -270,6 +270,62 @@ func TestBeaconState_NoDeadlock_Capella(t *testing.T) { wg.Wait() } +func TestBeaconState_NoDeadlock_Deneb(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + for i := uint64(1); i < count; i++ { + var someRoot [32]byte + var someKey [fieldparams.BLSPubkeyLength]byte + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + } + st, err := InitializeFromProtoUnsafeDeneb(ðpb.BeaconStateDeneb{ + Validators: vals, + }) + assert.NoError(t, err) + s, ok := st.(*BeaconState) + require.Equal(t, true, ok) + + wg := new(sync.WaitGroup) + + wg.Add(1) + go func() { + // Continuously lock and unlock the state + // by acquiring the lock. + for i := 0; i < 1000; i++ { + for _, f := range s.stateFieldLeaves { + f.Lock() + if f.Empty() { + f.InsertFieldLayer(make([][]*[32]byte, 10)) + } + f.Unlock() + f.FieldReference().AddRef() + } + } + wg.Done() + }() + // Constantly read from the offending portion + // of the code to ensure there is no possible + // recursive read locking. + for i := 0; i < 1000; i++ { + go func() { + _ = st.FieldReferencesCount() + }() + } + // Test will not terminate in the event of a deadlock. + wg.Wait() +} + func TestBeaconState_AppendBalanceWithTrie(t *testing.T) { count := uint64(100) vals := make([]*ethpb.Validator, 0, count) diff --git a/beacon-chain/state/state-native/state_trie.go b/beacon-chain/state/state-native/state_trie.go index 6e4ef31ffae6..ec70fd79da3e 100644 --- a/beacon-chain/state/state-native/state_trie.go +++ b/beacon-chain/state/state-native/state_trie.go @@ -83,11 +83,20 @@ var capellaFields = append( types.HistoricalSummaries, ) +var denebFields = append( + altairFields, + types.LatestExecutionPayloadHeaderDeneb, + types.NextWithdrawalIndex, + types.NextWithdrawalValidatorIndex, + types.HistoricalSummaries, +) + const ( phase0SharedFieldRefCount = 10 altairSharedFieldRefCount = 11 bellatrixSharedFieldRefCount = 12 capellaSharedFieldRefCount = 14 + denebSharedFieldRefCount = 14 ) // InitializeFromProtoPhase0 the beacon state from a protobuf representation. @@ -110,6 +119,11 @@ func InitializeFromProtoCapella(st *ethpb.BeaconStateCapella) (state.BeaconState return InitializeFromProtoUnsafeCapella(proto.Clone(st).(*ethpb.BeaconStateCapella)) } +// InitializeFromProtoDeneb the beacon state from a protobuf representation. +func InitializeFromProtoDeneb(st *ethpb.BeaconStateDeneb) (state.BeaconState, error) { + return InitializeFromProtoUnsafeDeneb(proto.Clone(st).(*ethpb.BeaconStateDeneb)) +} + // InitializeFromProtoUnsafePhase0 directly uses the beacon state protobuf fields // and sets them as fields of the BeaconState type. func InitializeFromProtoUnsafePhase0(st *ethpb.BeaconState) (state.BeaconState, error) { @@ -474,6 +488,102 @@ func InitializeFromProtoUnsafeCapella(st *ethpb.BeaconStateCapella) (state.Beaco return b, nil } +// InitializeFromProtoUnsafeDeneb directly uses the beacon state protobuf fields +// and sets them as fields of the BeaconState type. +func InitializeFromProtoUnsafeDeneb(st *ethpb.BeaconStateDeneb) (state.BeaconState, error) { + if st == nil { + return nil, errors.New("received nil state") + } + + var bRoots customtypes.BlockRoots + for i, r := range st.BlockRoots { + bRoots[i] = bytesutil.ToBytes32(r) + } + var sRoots customtypes.StateRoots + for i, r := range st.StateRoots { + sRoots[i] = bytesutil.ToBytes32(r) + } + hRoots := customtypes.HistoricalRoots(make([][32]byte, len(st.HistoricalRoots))) + for i, r := range st.HistoricalRoots { + hRoots[i] = bytesutil.ToBytes32(r) + } + var mixes customtypes.RandaoMixes + for i, m := range st.RandaoMixes { + mixes[i] = bytesutil.ToBytes32(m) + } + + fieldCount := params.BeaconConfig().BeaconStateCapellaFieldCount + b := &BeaconState{ + version: version.Deneb, + genesisTime: st.GenesisTime, + genesisValidatorsRoot: bytesutil.ToBytes32(st.GenesisValidatorsRoot), + slot: st.Slot, + fork: st.Fork, + latestBlockHeader: st.LatestBlockHeader, + blockRoots: &bRoots, + stateRoots: &sRoots, + historicalRoots: hRoots, + eth1Data: st.Eth1Data, + eth1DataVotes: st.Eth1DataVotes, + eth1DepositIndex: st.Eth1DepositIndex, + validators: st.Validators, + balances: st.Balances, + randaoMixes: &mixes, + slashings: st.Slashings, + previousEpochParticipation: st.PreviousEpochParticipation, + currentEpochParticipation: st.CurrentEpochParticipation, + justificationBits: st.JustificationBits, + previousJustifiedCheckpoint: st.PreviousJustifiedCheckpoint, + currentJustifiedCheckpoint: st.CurrentJustifiedCheckpoint, + finalizedCheckpoint: st.FinalizedCheckpoint, + inactivityScores: st.InactivityScores, + currentSyncCommittee: st.CurrentSyncCommittee, + nextSyncCommittee: st.NextSyncCommittee, + latestExecutionPayloadHeaderDeneb: st.LatestExecutionPayloadHeader, + nextWithdrawalIndex: st.NextWithdrawalIndex, + nextWithdrawalValidatorIndex: st.NextWithdrawalValidatorIndex, + historicalSummaries: st.HistoricalSummaries, + + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference, capellaSharedFieldRefCount), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + valMapHandler: stateutil.NewValMapHandler(st.Validators), + } + + for _, f := range denebFields { + b.dirtyFields[f] = true + b.rebuildTrie[f] = true + b.dirtyIndices[f] = []uint64{} + trie, err := fieldtrie.NewFieldTrie(f, types.BasicArray, nil, 0) + if err != nil { + return nil, err + } + b.stateFieldLeaves[f] = trie + } + + // Initialize field reference tracking for shared data. + b.sharedFieldReferences[types.BlockRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[types.StateRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[types.HistoricalRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Eth1DataVotes] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Validators] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Balances] = stateutil.NewRef(1) + b.sharedFieldReferences[types.RandaoMixes] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Slashings] = stateutil.NewRef(1) + b.sharedFieldReferences[types.PreviousEpochParticipationBits] = stateutil.NewRef(1) + b.sharedFieldReferences[types.CurrentEpochParticipationBits] = stateutil.NewRef(1) + b.sharedFieldReferences[types.InactivityScores] = stateutil.NewRef(1) + b.sharedFieldReferences[types.LatestExecutionPayloadHeaderDeneb] = stateutil.NewRef(1) // New in Deneb. + b.sharedFieldReferences[types.HistoricalSummaries] = stateutil.NewRef(1) // New in Capella. + + state.StateCount.Inc() + // Finalizer runs when dst is being destroyed in garbage collection. + runtime.SetFinalizer(b, finalizerCleanup) + return b, nil +} + // Copy returns a deep copy of the beacon state. func (b *BeaconState) Copy() state.BeaconState { b.lock.RLock() @@ -489,6 +599,8 @@ func (b *BeaconState) Copy() state.BeaconState { fieldCount = params.BeaconConfig().BeaconStateBellatrixFieldCount case version.Capella: fieldCount = params.BeaconConfig().BeaconStateCapellaFieldCount + case version.Deneb: + fieldCount = params.BeaconConfig().BeaconStateCapellaFieldCount } dst := &BeaconState{ @@ -532,6 +644,7 @@ func (b *BeaconState) Copy() state.BeaconState { nextSyncCommittee: b.nextSyncCommitteeVal(), latestExecutionPayloadHeader: b.latestExecutionPayloadHeaderVal(), latestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapellaVal(), + latestExecutionPayloadHeaderDeneb: b.latestExecutionPayloadHeaderDenebVal(), dirtyFields: make(map[types.FieldIndex]bool, fieldCount), dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), @@ -551,6 +664,8 @@ func (b *BeaconState) Copy() state.BeaconState { dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, bellatrixSharedFieldRefCount) case version.Capella: dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, capellaSharedFieldRefCount) + case version.Deneb: + dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, denebSharedFieldRefCount) } for field, ref := range b.sharedFieldReferences { @@ -640,6 +755,8 @@ func (b *BeaconState) initializeMerkleLayers(ctx context.Context) error { b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateBellatrixFieldCount) case version.Capella: b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateCapellaFieldCount) + case version.Deneb: + b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateCapellaFieldCount) } return nil @@ -830,6 +947,8 @@ func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex) return b.latestExecutionPayloadHeader.HashTreeRoot() case types.LatestExecutionPayloadHeaderCapella: return b.latestExecutionPayloadHeaderCapella.HashTreeRoot() + case types.LatestExecutionPayloadHeaderDeneb: + return b.latestExecutionPayloadHeaderDeneb.HashTreeRoot() case types.NextWithdrawalIndex: return ssz.Uint64Root(b.nextWithdrawalIndex), nil case types.NextWithdrawalValidatorIndex: diff --git a/beacon-chain/state/state-native/state_trie_test.go b/beacon-chain/state/state-native/state_trie_test.go index adb1a09bd66c..9105a6ddefd4 100644 --- a/beacon-chain/state/state-native/state_trie_test.go +++ b/beacon-chain/state/state-native/state_trie_test.go @@ -166,6 +166,42 @@ func TestInitializeFromProto_Capella(t *testing.T) { } } +func TestInitializeFromProto_Deneb(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateDeneb + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconStateDeneb{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateDeneb{}, + }, + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := statenative.InitializeFromProtoDeneb(tt.state) + if tt.error != "" { + require.ErrorContains(t, tt.error, err) + } else { + require.NoError(t, err) + } + }) + } +} + func TestInitializeFromProtoUnsafe_Phase0(t *testing.T) { testState, _ := util.DeterministicGenesisState(t, 64) pbState, err := statenative.ProtobufBeaconStatePhase0(testState.ToProtoUnsafe()) @@ -297,6 +333,37 @@ func TestInitializeFromProtoUnsafe_Capella(t *testing.T) { } } +func TestInitializeFromProtoUnsafe_Deneb(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateDeneb + error string + } + initTests := []test{ + { + name: "nil validators", + state: ðpb.BeaconStateDeneb{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateDeneb{}, + }, + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := statenative.InitializeFromProtoUnsafeDeneb(tt.state) + if tt.error != "" { + assert.ErrorContains(t, tt.error, err) + } else { + assert.NoError(t, err) + } + }) + } +} + func TestBeaconState_HashTreeRoot(t *testing.T) { testState, _ := util.DeterministicGenesisState(t, 64) diff --git a/beacon-chain/state/state-native/types/types.go b/beacon-chain/state/state-native/types/types.go index 3be57db9cb92..d181bce1bc4e 100644 --- a/beacon-chain/state/state-native/types/types.go +++ b/beacon-chain/state/state-native/types/types.go @@ -146,7 +146,7 @@ func (f FieldIndex) RealPosition() int { return 22 case NextSyncCommittee: return 23 - case LatestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella: + case LatestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella, LatestExecutionPayloadHeaderDeneb: return 24 case NextWithdrawalIndex: return 25 @@ -205,6 +205,7 @@ const ( NextSyncCommittee LatestExecutionPayloadHeader LatestExecutionPayloadHeaderCapella + LatestExecutionPayloadHeaderDeneb NextWithdrawalIndex NextWithdrawalValidatorIndex HistoricalSummaries diff --git a/beacon-chain/state/stategen/replay.go b/beacon-chain/state/stategen/replay.go index 09ee478d3bfb..2562a38f0d45 100644 --- a/beacon-chain/state/stategen/replay.go +++ b/beacon-chain/state/stategen/replay.go @@ -205,7 +205,7 @@ func ReplayProcessSlots(ctx context.Context, state state.BeaconState, slot primi tracing.AnnotateError(span, err) return nil, errors.Wrap(err, "could not process epoch with optimizations") } - case version.Altair, version.Bellatrix, version.Capella: + case version.Altair, version.Bellatrix, version.Capella, version.Deneb: state, err = altair.ProcessEpoch(ctx, state) if err != nil { tracing.AnnotateError(span, err) diff --git a/config/params/config.go b/config/params/config.go index e78ace5900e1..69bc0b0529d6 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -153,6 +153,8 @@ type BeaconChainConfig struct { BellatrixForkEpoch primitives.Epoch `yaml:"BELLATRIX_FORK_EPOCH" spec:"true"` // BellatrixForkEpoch is used to represent the assigned fork epoch for bellatrix. CapellaForkVersion []byte `yaml:"CAPELLA_FORK_VERSION" spec:"true"` // CapellaForkVersion is used to represent the fork version for capella. CapellaForkEpoch primitives.Epoch `yaml:"CAPELLA_FORK_EPOCH" spec:"true"` // CapellaForkEpoch is used to represent the assigned fork epoch for capella. + DenebForkVersion []byte `yaml:"DENEB_FORK_VERSION" spec:"true"` // DenebForkVersion is used to represent the fork version for deneb. + DenebForkEpoch primitives.Epoch `yaml:"DENEB_FORK_EPOCH" spec:"true"` // DenebForkEpoch is used to represent the assigned fork epoch for deneb. ForkVersionSchedule map[[fieldparams.VersionLength]byte]primitives.Epoch // Schedule of fork epochs by version. ForkVersionNames map[[fieldparams.VersionLength]byte]string // Human-readable names of fork versions. diff --git a/config/params/loader_test.go b/config/params/loader_test.go index e291831ae024..f275b741f2d5 100644 --- a/config/params/loader_test.go +++ b/config/params/loader_test.go @@ -20,7 +20,7 @@ import ( // Variables defined in the placeholderFields will not be tested in `TestLoadConfigFile`. // These are variables that we don't use in Prysm. (i.e. future hardfork, light client... etc) -var placeholderFields = []string{"UPDATE_TIMEOUT", "DENEB_FORK_EPOCH", "DENEB_FORK_VERSION"} +var placeholderFields = []string{"UPDATE_TIMEOUT"} func assertEqualConfigs(t *testing.T, name string, fields []string, expected, actual *params.BeaconChainConfig) { // Misc params. diff --git a/config/params/mainnet_config.go b/config/params/mainnet_config.go index bba249f05389..8f98acb42379 100644 --- a/config/params/mainnet_config.go +++ b/config/params/mainnet_config.go @@ -211,6 +211,8 @@ var mainnetBeaconConfig = &BeaconChainConfig{ BellatrixForkEpoch: mainnetBellatrixForkEpoch, CapellaForkVersion: []byte{3, 0, 0, 0}, CapellaForkEpoch: 194048, + DenebForkVersion: []byte{4, 0, 0, 0}, + DenebForkEpoch: math.MaxUint64, // New values introduced in Altair hard fork 1. // Participation flag indices. diff --git a/config/params/minimal_config.go b/config/params/minimal_config.go index f41d9ae8a8a6..93b1156dba10 100644 --- a/config/params/minimal_config.go +++ b/config/params/minimal_config.go @@ -88,6 +88,7 @@ func MinimalSpecConfig() *BeaconChainConfig { minimalConfig.BellatrixForkEpoch = math.MaxUint64 minimalConfig.CapellaForkVersion = []byte{3, 0, 0, 1} minimalConfig.CapellaForkEpoch = math.MaxUint64 + minimalConfig.DenebForkVersion = []byte{4, 0, 0, 1} minimalConfig.SyncCommitteeSize = 32 minimalConfig.InactivityScoreBias = 4 diff --git a/config/params/testnet_prater_config.go b/config/params/testnet_prater_config.go index 8f48aa5b7a89..58a15c6685da 100644 --- a/config/params/testnet_prater_config.go +++ b/config/params/testnet_prater_config.go @@ -1,6 +1,8 @@ package params import ( + "math" + eth1Params "github.com/ethereum/go-ethereum/params" ) @@ -40,6 +42,8 @@ func PraterConfig() *BeaconChainConfig { cfg.BellatrixForkVersion = []byte{0x2, 0x0, 0x10, 0x20} cfg.CapellaForkEpoch = 162304 cfg.CapellaForkVersion = []byte{0x3, 0x0, 0x10, 0x20} + cfg.DenebForkEpoch = math.MaxUint64 + cfg.DenebForkVersion = []byte{0x4, 0x0, 0x10, 0x20} cfg.TerminalTotalDifficulty = "10790000" cfg.DepositContractAddress = "0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b" cfg.InitializeForkSchedule() diff --git a/testing/spectest/shared/deneb/ssz_static/BUILD.bazel b/testing/spectest/shared/deneb/ssz_static/BUILD.bazel index 1ced5feba1a0..f6ad338fc8e1 100644 --- a/testing/spectest/shared/deneb/ssz_static/BUILD.bazel +++ b/testing/spectest/shared/deneb/ssz_static/BUILD.bazel @@ -7,8 +7,10 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/v4/testing/spectest/shared/deneb/ssz_static", visibility = ["//testing/spectest:__subpackages__"], deps = [ + "//beacon-chain/state/state-native:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//testing/require:go_default_library", "//testing/spectest/shared/common/ssz_static:go_default_library", "@com_github_prysmaticlabs_fastssz//:go_default_library", ], diff --git a/testing/spectest/shared/deneb/ssz_static/ssz_static.go b/testing/spectest/shared/deneb/ssz_static/ssz_static.go index 878073ed6e37..86eaafcd4f6b 100644 --- a/testing/spectest/shared/deneb/ssz_static/ssz_static.go +++ b/testing/spectest/shared/deneb/ssz_static/ssz_static.go @@ -1,18 +1,33 @@ package ssz_static import ( + "context" "errors" "testing" fssz "github.com/prysmaticlabs/fastssz" + state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native" enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v4/testing/require" common "github.com/prysmaticlabs/prysm/v4/testing/spectest/shared/common/ssz_static" ) // RunSSZStaticTests executes "ssz_static" tests. func RunSSZStaticTests(t *testing.T, config string) { - common.RunSSZStaticTests(t, config, "deneb", unmarshalledSSZ, nil) + common.RunSSZStaticTests(t, config, "deneb", unmarshalledSSZ, customHtr) +} + +func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR { + switch object.(type) { + case *ethpb.BeaconStateDeneb: + htrs = append(htrs, func(s interface{}) ([32]byte, error) { + beaconState, err := state_native.InitializeFromProtoDeneb(s.(*ethpb.BeaconStateDeneb)) + require.NoError(t, err) + return beaconState.HashTreeRoot(context.Background()) + }) + } + return htrs } // unmarshalledSSZ unmarshalls serialized input. diff --git a/testing/util/BUILD.bazel b/testing/util/BUILD.bazel index 685a33517bd8..7fa3617924ee 100644 --- a/testing/util/BUILD.bazel +++ b/testing/util/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "block.go", "capella_block.go", "capella_state.go", + "deneb_state.go", "deposits.go", "helpers.go", "merge.go", diff --git a/testing/util/deneb_state.go b/testing/util/deneb_state.go new file mode 100644 index 000000000000..6169a62685e4 --- /dev/null +++ b/testing/util/deneb_state.go @@ -0,0 +1,252 @@ +package util + +import ( + "context" + "testing" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" + state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stateutil" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" + "github.com/prysmaticlabs/prysm/v4/config/params" + "github.com/prysmaticlabs/prysm/v4/crypto/bls" + enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1" + ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" +) + +// DeterministicGenesisStateDeneb returns a genesis state in Deneb format made using the deterministic deposits. +func DeterministicGenesisStateDeneb(t testing.TB, numValidators uint64) (state.BeaconState, []bls.SecretKey) { + deposits, privKeys, err := DeterministicDepositsAndKeys(numValidators) + if err != nil { + t.Fatal(errors.Wrapf(err, "failed to get %d deposits", numValidators)) + } + eth1Data, err := DeterministicEth1Data(len(deposits)) + if err != nil { + t.Fatal(errors.Wrapf(err, "failed to get eth1data for %d deposits", numValidators)) + } + beaconState, err := genesisBeaconStateDeneb(context.Background(), deposits, uint64(0), eth1Data) + if err != nil { + t.Fatal(errors.Wrapf(err, "failed to get genesis beacon state of %d validators", numValidators)) + } + resetCache() + return beaconState, privKeys +} + +// genesisBeaconStateDeneb returns the genesis beacon state. +func genesisBeaconStateDeneb(ctx context.Context, deposits []*ethpb.Deposit, genesisTime uint64, eth1Data *ethpb.Eth1Data) (state.BeaconState, error) { + st, err := emptyGenesisStateDeneb() + if err != nil { + return nil, err + } + + // Process initial deposits. + st, err = helpers.UpdateGenesisEth1Data(st, deposits, eth1Data) + if err != nil { + return nil, err + } + + st, err = processPreGenesisDeposits(ctx, st, deposits) + if err != nil { + return nil, errors.Wrap(err, "could not process validator deposits") + } + + return buildGenesisBeaconStateDeneb(genesisTime, st, st.Eth1Data()) +} + +// emptyGenesisStateDeneb returns an empty genesis state in Deneb format. +func emptyGenesisStateDeneb() (state.BeaconState, error) { + st := ðpb.BeaconStateDeneb{ + // Misc fields. + Slot: 0, + Fork: ðpb.Fork{ + PreviousVersion: params.BeaconConfig().BellatrixForkVersion, + CurrentVersion: params.BeaconConfig().DenebForkVersion, + Epoch: 0, + }, + // Validator registry fields. + Validators: []*ethpb.Validator{}, + Balances: []uint64{}, + InactivityScores: []uint64{}, + + JustificationBits: []byte{0}, + HistoricalRoots: [][]byte{}, + CurrentEpochParticipation: []byte{}, + PreviousEpochParticipation: []byte{}, + + // Eth1 data. + Eth1Data: ðpb.Eth1Data{}, + Eth1DataVotes: []*ethpb.Eth1Data{}, + Eth1DepositIndex: 0, + + LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderDeneb{}, + } + return state_native.InitializeFromProtoDeneb(st) +} + +func buildGenesisBeaconStateDeneb(genesisTime uint64, preState state.BeaconState, eth1Data *ethpb.Eth1Data) (state.BeaconState, error) { + if eth1Data == nil { + return nil, errors.New("no eth1data provided for genesis state") + } + + randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(randaoMixes); i++ { + h := make([]byte, 32) + copy(h, eth1Data.BlockHash) + randaoMixes[i] = h + } + + zeroHash := params.BeaconConfig().ZeroHash[:] + + activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(activeIndexRoots); i++ { + activeIndexRoots[i] = zeroHash + } + + blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(blockRoots); i++ { + blockRoots[i] = zeroHash + } + + stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(stateRoots); i++ { + stateRoots[i] = zeroHash + } + + slashings := make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector) + + genesisValidatorsRoot, err := stateutil.ValidatorRegistryRoot(preState.Validators()) + if err != nil { + return nil, errors.Wrapf(err, "could not hash tree root genesis validators %v", err) + } + + prevEpochParticipation, err := preState.PreviousEpochParticipation() + if err != nil { + return nil, err + } + currEpochParticipation, err := preState.CurrentEpochParticipation() + if err != nil { + return nil, err + } + scores, err := preState.InactivityScores() + if err != nil { + return nil, err + } + st := ðpb.BeaconStateDeneb{ + // Misc fields. + Slot: 0, + GenesisTime: genesisTime, + GenesisValidatorsRoot: genesisValidatorsRoot[:], + + Fork: ðpb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + Epoch: 0, + }, + + // Validator registry fields. + Validators: preState.Validators(), + Balances: preState.Balances(), + PreviousEpochParticipation: prevEpochParticipation, + CurrentEpochParticipation: currEpochParticipation, + InactivityScores: scores, + + // Randomness and committees. + RandaoMixes: randaoMixes, + + // Finality. + PreviousJustifiedCheckpoint: ðpb.Checkpoint{ + Epoch: 0, + Root: params.BeaconConfig().ZeroHash[:], + }, + CurrentJustifiedCheckpoint: ðpb.Checkpoint{ + Epoch: 0, + Root: params.BeaconConfig().ZeroHash[:], + }, + JustificationBits: []byte{0}, + FinalizedCheckpoint: ðpb.Checkpoint{ + Epoch: 0, + Root: params.BeaconConfig().ZeroHash[:], + }, + + HistoricalRoots: [][]byte{}, + BlockRoots: blockRoots, + StateRoots: stateRoots, + Slashings: slashings, + + // Eth1 data. + Eth1Data: eth1Data, + Eth1DataVotes: []*ethpb.Eth1Data{}, + Eth1DepositIndex: preState.Eth1DepositIndex(), + } + + var scBits [fieldparams.SyncAggregateSyncCommitteeBytesLength]byte + bodyRoot, err := (ðpb.BeaconBlockBodyDeneb{ + RandaoReveal: make([]byte, 96), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: make([]byte, 32), + BlockHash: make([]byte, 32), + }, + Graffiti: make([]byte, 32), + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: scBits[:], + SyncCommitteeSignature: make([]byte, 96), + }, + ExecutionPayload: &enginev1.ExecutionPayloadDeneb{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, 32), + ReceiptsRoot: make([]byte, 32), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, 32), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + ExcessDataGas: make([]byte, 32), + }, + }).HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not hash tree root empty block body") + } + + st.LatestBlockHeader = ðpb.BeaconBlockHeader{ + ParentRoot: zeroHash, + StateRoot: zeroHash, + BodyRoot: bodyRoot[:], + } + + var pubKeys [][]byte + vals := preState.Validators() + for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ { + j := i % uint64(len(vals)) + pubKeys = append(pubKeys, vals[j].PublicKey) + } + aggregated, err := bls.AggregatePublicKeys(pubKeys) + if err != nil { + return nil, err + } + st.CurrentSyncCommittee = ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: aggregated.Marshal(), + } + st.NextSyncCommittee = ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: aggregated.Marshal(), + } + + st.LatestExecutionPayloadHeader = &enginev1.ExecutionPayloadHeaderDeneb{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, 32), + ReceiptsRoot: make([]byte, 32), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, 32), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + TransactionsRoot: make([]byte, 32), + WithdrawalsRoot: make([]byte, 32), + ExcessDataGas: make([]byte, 32), + } + + return state_native.InitializeFromProtoDeneb(st) +}