Skip to content

Commit

Permalink
Merge branch '__develop' into eip-7549-vc
Browse files Browse the repository at this point in the history
  • Loading branch information
rkapka committed Jul 10, 2024
2 parents af85fac + 7c81c7d commit c14f11e
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 37 deletions.
23 changes: 19 additions & 4 deletions beacon-chain/core/blocks/exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ func ProcessVoluntaryExits(
// assert get_current_epoch(state) >= voluntary_exit.epoch
// # Verify the validator has been active long enough
// assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD
// # Only exit validator if it has no pending withdrawals in the queue
// assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in Electra:EIP7251]
// # Verify signature
// domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
// signing_root = compute_signing_root(voluntary_exit, domain)
Expand All @@ -113,7 +115,6 @@ func VerifyExitAndSignature(
return errors.New("nil exit")
}

currentSlot := state.Slot()
fork := state.Fork()
genesisRoot := state.GenesisValidatorsRoot()

Expand All @@ -128,7 +129,7 @@ func VerifyExitAndSignature(
}

exit := signed.Exit
if err := verifyExitConditions(validator, currentSlot, exit); err != nil {
if err := verifyExitConditions(state, validator, exit); err != nil {
return err
}
domain, err := signing.Domain(fork, exit.Epoch, params.BeaconConfig().DomainVoluntaryExit, genesisRoot)
Expand Down Expand Up @@ -157,14 +158,16 @@ func VerifyExitAndSignature(
// assert get_current_epoch(state) >= voluntary_exit.epoch
// # Verify the validator has been active long enough
// assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD
// # Only exit validator if it has no pending withdrawals in the queue
// assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in Electra:EIP7251]
// # Verify signature
// domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
// signing_root = compute_signing_root(voluntary_exit, domain)
// assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
// # Initiate exit
// initiate_validator_exit(state, voluntary_exit.validator_index)
func verifyExitConditions(validator state.ReadOnlyValidator, currentSlot primitives.Slot, exit *ethpb.VoluntaryExit) error {
currentEpoch := slots.ToEpoch(currentSlot)
func verifyExitConditions(st state.ReadOnlyBeaconState, validator state.ReadOnlyValidator, exit *ethpb.VoluntaryExit) error {
currentEpoch := slots.ToEpoch(st.Slot())
// Verify the validator is active.
if !helpers.IsActiveValidatorUsingTrie(validator, currentEpoch) {
return errors.New("non-active validator cannot exit")
Expand All @@ -187,5 +190,17 @@ func verifyExitConditions(validator state.ReadOnlyValidator, currentSlot primiti
validator.ActivationEpoch()+params.BeaconConfig().ShardCommitteePeriod,
)
}

if st.Version() >= version.Electra {
// Only exit validator if it has no pending withdrawals in the queue.
pbw, err := st.PendingBalanceToWithdraw(exit.ValidatorIndex)
if err != nil {
return fmt.Errorf("unable to retrieve pending balance to withdraw for validator %d: %w", exit.ValidatorIndex, err)
}
if pbw != 0 {
return fmt.Errorf("validator %d must have no pending balance to withdraw, got %d pending balance to withdraw", exit.ValidatorIndex, pbw)
}
}

return nil
}
53 changes: 52 additions & 1 deletion beacon-chain/core/blocks/exit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ func TestProcessVoluntaryExits_AppliesCorrectStatus(t *testing.T) {
}

func TestVerifyExitAndSignature(t *testing.T) {
// Remove after electra fork epoch is defined.
cfg := params.BeaconConfig()
cfg.ElectraForkEpoch = cfg.DenebForkEpoch * 2
params.SetActiveTestCleanup(t, cfg)
// End remove section.
denebSlot, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
require.NoError(t, err)
tests := []struct {
Expand Down Expand Up @@ -167,7 +172,7 @@ func TestVerifyExitAndSignature(t *testing.T) {
wantErr: "nil exit",
},
{
name: "Happy Path",
name: "Happy Path phase0",
setup: func() (*ethpb.Validator, *ethpb.SignedVoluntaryExit, state.ReadOnlyBeaconState, error) {
fork := &ethpb.Fork{
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
Expand Down Expand Up @@ -275,6 +280,52 @@ func TestVerifyExitAndSignature(t *testing.T) {
return validator, signedExit, bs, nil
},
},
{
name: "EIP-7251 - pending balance to withdraw must be zero",
setup: func() (*ethpb.Validator, *ethpb.SignedVoluntaryExit, state.ReadOnlyBeaconState, error) {
fork := &ethpb.Fork{
PreviousVersion: params.BeaconConfig().DenebForkVersion,
CurrentVersion: params.BeaconConfig().ElectraForkVersion,
Epoch: params.BeaconConfig().ElectraForkEpoch,
}
signedExit := &ethpb.SignedVoluntaryExit{
Exit: &ethpb.VoluntaryExit{
Epoch: params.BeaconConfig().DenebForkEpoch + 1,
ValidatorIndex: 0,
},
}
electraSlot, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch)
require.NoError(t, err)
bs, keys := util.DeterministicGenesisState(t, 1)
bs, err = state_native.InitializeFromProtoUnsafeElectra(&ethpb.BeaconStateElectra{
GenesisValidatorsRoot: bs.GenesisValidatorsRoot(),
Fork: fork,
Slot: electraSlot,
Validators: bs.Validators(),
})
if err != nil {
return nil, nil, nil, err
}
validator := bs.Validators()[0]
validator.ActivationEpoch = 1
err = bs.UpdateValidatorAtIndex(0, validator)
require.NoError(t, err)
sb, err := signing.ComputeDomainAndSign(bs, signedExit.Exit.Epoch, signedExit.Exit, params.BeaconConfig().DomainVoluntaryExit, keys[0])
require.NoError(t, err)
sig, err := bls.SignatureFromBytes(sb)
require.NoError(t, err)
signedExit.Signature = sig.Marshal()

// Give validator a pending balance to withdraw.
require.NoError(t, bs.AppendPendingPartialWithdrawal(&ethpb.PendingPartialWithdrawal{
Index: 0,
Amount: 500,
}))

return validator, signedExit, bs, nil
},
wantErr: "validator 0 must have no pending balance to withdraw",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/core/electra/registry_updates.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) error {
}

// Collect validators eligible for activation and not yet dequeued for activation.
if helpers.IsEligibleForActivationUsingTrie(st, val) {
if helpers.IsEligibleForActivationUsingROVal(st, val) {
eligibleForActivation = append(eligibleForActivation, primitives.ValidatorIndex(idx))
}

Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/core/epoch/epoch_processing.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) (state.Be
}

// Collect validators eligible for activation and not yet dequeued for activation.
if helpers.IsEligibleForActivationUsingTrie(st, val) {
if helpers.IsEligibleForActivationUsingROVal(st, val) {
eligibleForActivation = append(eligibleForActivation, primitives.ValidatorIndex(idx))
}

Expand Down
10 changes: 3 additions & 7 deletions beacon-chain/core/helpers/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,13 +470,9 @@ func IsEligibleForActivation(state state.ReadOnlyCheckpoint, validator *ethpb.Va
return isEligibleForActivation(validator.ActivationEligibilityEpoch, validator.ActivationEpoch, finalizedEpoch)
}

// IsEligibleForActivationUsingTrie checks if the validator is eligible for activation.
func IsEligibleForActivationUsingTrie(state state.ReadOnlyCheckpoint, validator state.ReadOnlyValidator) bool {
cpt := state.FinalizedCheckpoint()
if cpt == nil {
return false
}
return isEligibleForActivation(validator.ActivationEligibilityEpoch(), validator.ActivationEpoch(), cpt.Epoch)
// IsEligibleForActivationUsingROVal checks if the validator is eligible for activation using the provided read only validator.
func IsEligibleForActivationUsingROVal(state state.ReadOnlyCheckpoint, validator state.ReadOnlyValidator) bool {
return isEligibleForActivation(validator.ActivationEligibilityEpoch(), validator.ActivationEpoch(), state.FinalizedCheckpointEpoch())
}

// isEligibleForActivation carries out the logic for IsEligibleForActivation*
Expand Down
3 changes: 2 additions & 1 deletion beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,12 @@ func (f *ForkChoice) updateBalances() error {
newBalances := f.justifiedBalances
zHash := params.BeaconConfig().ZeroHash

for index, vote := range f.votes {
for index := 0; index < len(f.votes); index++ {
// Skip if validator has been slashed
if f.store.slashedIndices[primitives.ValidatorIndex(index)] {
continue
}
vote := &f.votes[index]
// Skip if validator has never voted for current root and next root (i.e. if the
// votes are zero hash aka genesis block), there's nothing to compute.
if vote.currentRoot == zHash && vote.nextRoot == zHash {
Expand Down
42 changes: 25 additions & 17 deletions beacon-chain/p2p/gossip_topic_mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ import (

// gossipTopicMappings represent the protocol ID to protobuf message type map for easy
// lookup.
var gossipTopicMappings = map[string]proto.Message{
BlockSubnetTopicFormat: &ethpb.SignedBeaconBlock{},
AttestationSubnetTopicFormat: &ethpb.Attestation{},
ExitSubnetTopicFormat: &ethpb.SignedVoluntaryExit{},
ProposerSlashingSubnetTopicFormat: &ethpb.ProposerSlashing{},
AttesterSlashingSubnetTopicFormat: &ethpb.AttesterSlashing{},
AggregateAndProofSubnetTopicFormat: &ethpb.SignedAggregateAttestationAndProof{},
SyncContributionAndProofSubnetTopicFormat: &ethpb.SignedContributionAndProof{},
SyncCommitteeSubnetTopicFormat: &ethpb.SyncCommitteeMessage{},
BlsToExecutionChangeSubnetTopicFormat: &ethpb.SignedBLSToExecutionChange{},
BlobSubnetTopicFormat: &ethpb.BlobSidecar{},
var gossipTopicMappings = map[string]func() proto.Message{
BlockSubnetTopicFormat: func() proto.Message { return &ethpb.SignedBeaconBlock{} },
AttestationSubnetTopicFormat: func() proto.Message { return &ethpb.Attestation{} },
ExitSubnetTopicFormat: func() proto.Message { return &ethpb.SignedVoluntaryExit{} },
ProposerSlashingSubnetTopicFormat: func() proto.Message { return &ethpb.ProposerSlashing{} },
AttesterSlashingSubnetTopicFormat: func() proto.Message { return &ethpb.AttesterSlashing{} },
AggregateAndProofSubnetTopicFormat: func() proto.Message { return &ethpb.SignedAggregateAttestationAndProof{} },
SyncContributionAndProofSubnetTopicFormat: func() proto.Message { return &ethpb.SignedContributionAndProof{} },
SyncCommitteeSubnetTopicFormat: func() proto.Message { return &ethpb.SyncCommitteeMessage{} },
BlsToExecutionChangeSubnetTopicFormat: func() proto.Message { return &ethpb.SignedBLSToExecutionChange{} },
BlobSubnetTopicFormat: func() proto.Message { return &ethpb.BlobSidecar{} },
}

// GossipTopicMappings is a function to return the assigned data type
Expand All @@ -44,27 +44,35 @@ func GossipTopicMappings(topic string, epoch primitives.Epoch) proto.Message {
if epoch >= params.BeaconConfig().AltairForkEpoch {
return &ethpb.SignedBeaconBlockAltair{}
}
return gossipTopicMappings[topic]
return gossipMessage(topic)
case AttestationSubnetTopicFormat:
if epoch >= params.BeaconConfig().ElectraForkEpoch {
return &ethpb.AttestationElectra{}
}
return gossipTopicMappings[topic]
return gossipMessage(topic)
case AttesterSlashingSubnetTopicFormat:
if epoch >= params.BeaconConfig().ElectraForkEpoch {
return &ethpb.AttesterSlashingElectra{}
}
return gossipTopicMappings[topic]
return gossipMessage(topic)
case AggregateAndProofSubnetTopicFormat:
if epoch >= params.BeaconConfig().ElectraForkEpoch {
return &ethpb.SignedAggregateAttestationAndProofElectra{}
}
return gossipTopicMappings[topic]
return gossipMessage(topic)
default:
return gossipTopicMappings[topic]
return gossipMessage(topic)
}
}

func gossipMessage(topic string) proto.Message {
msgGen, ok := gossipTopicMappings[topic]
if !ok {
return nil
}
return msgGen()
}

// AllTopics returns all topics stored in our
// gossip mapping.
func AllTopics() []string {
Expand All @@ -81,7 +89,7 @@ var GossipTypeMapping = make(map[reflect.Type]string, len(gossipTopicMappings))

func init() {
for k, v := range gossipTopicMappings {
GossipTypeMapping[reflect.TypeOf(v)] = k
GossipTypeMapping[reflect.TypeOf(v())] = k
}
// Specially handle Altair objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockAltair{})] = BlockSubnetTopicFormat
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/p2p/gossip_topic_mappings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestMappingHasNoDuplicates(t *testing.T) {
params.SetupTestConfigCleanup(t)
m := make(map[reflect.Type]bool)
for _, v := range gossipTopicMappings {
if _, ok := m[reflect.TypeOf(v)]; ok {
if _, ok := m[reflect.TypeOf(v())]; ok {
t.Errorf("%T is duplicated in the topic mapping", v)
}
m[reflect.TypeOf(v)] = true
Expand Down
3 changes: 1 addition & 2 deletions beacon-chain/sync/decode_pubsub.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"google.golang.org/protobuf/proto"
)

var errNilPubsubMessage = errors.New("nil pubsub message")
Expand Down Expand Up @@ -52,7 +51,7 @@ func (s *Service) decodePubsubMessage(msg *pubsub.Message) (ssz.Unmarshaler, err
if base == nil {
return nil, p2p.ErrMessageNotMapped
}
m, ok := proto.Clone(base).(ssz.Unmarshaler)
m, ok := base.(ssz.Unmarshaler)
if !ok {
return nil, errors.Errorf("message of %T does not support marshaller interface", base)
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/beacon-chain/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ func before(ctx *cli.Context) error {
return errors.Wrap(err, "failed to set max fd limits")
}

if err := features.ValidateNetworkFlags(ctx); err != nil {
return errors.Wrap(err, "provided multiple network flags")
}

return cmd.ValidateNoArgs(ctx)
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/prysmctl/validator/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ var Commands = []*cli.Command{
if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil {
return err
}
if err := features.ValidateNetworkFlags(cliCtx); err != nil {
return err
}
if err := tos.VerifyTosAcceptedOrPrompt(cliCtx); err != nil {
return err
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/validator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ func main() {
return errors.Wrap(err, "failed to setup debug")
}

if err := features.ValidateNetworkFlags(ctx); err != nil {
return errors.Wrap(err, "provided multiple network flags")
}

return cmd.ValidateNoArgs(ctx)
},
After: func(ctx *cli.Context) error {
Expand Down
24 changes: 24 additions & 0 deletions config/features/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The process for implementing new features using this package is as follows:
package features

import (
"fmt"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -337,3 +339,25 @@ func logDisabled(flag cli.DocGenerationFlag) {
}
log.WithField(name, flag.GetUsage()).Warn(disabledFeatureFlag)
}

// ValidateNetworkFlags validates provided flags and
// prevents beacon node or validator to start
// if more than one network flag is provided
func ValidateNetworkFlags(ctx *cli.Context) error {
networkFlagsCount := 0
for _, flag := range NetworkFlags {
if ctx.IsSet(flag.Names()[0]) {
networkFlagsCount++
if networkFlagsCount > 1 {
// using a forLoop so future addition
// doesn't require changes in this function
var flagNames []string
for _, flag := range NetworkFlags {
flagNames = append(flagNames, "--"+flag.Names()[0])
}
return fmt.Errorf("cannot use more than one network flag at the same time. Possible network flags are: %s", strings.Join(flagNames, ", "))
}
}
}
return nil
}
Loading

0 comments on commit c14f11e

Please sign in to comment.