diff --git a/beacon-chain/core/blocks/block_operations.go b/beacon-chain/core/blocks/block_operations.go index 09b442259..f735c9051 100644 --- a/beacon-chain/core/blocks/block_operations.go +++ b/beacon-chain/core/blocks/block_operations.go @@ -960,7 +960,10 @@ func ProcessVoluntaryExits(ctx context.Context, beaconState *pb.BeaconState, bod exits := body.VoluntaryExits for idx, exit := range exits { - if err := VerifyExit(beaconState, exit); err != nil { + if int(exit.Exit.ValidatorIndex) >= len(beaconState.Validators) { + return nil, fmt.Errorf("validator index out of bound %d > %d", exit.Exit.ValidatorIndex, len(beaconState.Validators)) + } + if err := VerifyExit(beaconState.Validators[exit.Exit.ValidatorIndex], beaconState.Slot, beaconState.Fork, exit); err != nil { return nil, errors.Wrapf(err, "could not verify exit %d", idx) } beaconState, err = v.InitiateValidatorExit(beaconState, exit.Exit.ValidatorIndex) @@ -1008,18 +1011,14 @@ func ProcessVoluntaryExitsNoVerify( // # Verify signature // domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, exit.epoch) // assert bls_verify(validator.pubkey, signing_root(exit), exit.signature, domain) -func VerifyExit(beaconState *pb.BeaconState, signed *ethpb.SignedVoluntaryExit) error { +func VerifyExit(validator *ethpb.Validator, currentSlot uint64, fork *pb.Fork, signed *ethpb.SignedVoluntaryExit) error { if signed == nil || signed.Exit == nil { return errors.New("nil exit") } exit := signed.Exit - if int(exit.ValidatorIndex) >= len(beaconState.Validators) { - return fmt.Errorf("validator index out of bound %d > %d", exit.ValidatorIndex, len(beaconState.Validators)) - } - validator := beaconState.Validators[exit.ValidatorIndex] - currentEpoch := helpers.CurrentEpoch(beaconState) + currentEpoch := helpers.SlotToEpoch(currentSlot) // Verify the validator is active. if !helpers.IsActiveValidator(validator, currentEpoch) { return errors.New("non-active validator cannot exit") @@ -1040,7 +1039,7 @@ func VerifyExit(beaconState *pb.BeaconState, signed *ethpb.SignedVoluntaryExit) validator.ActivationEpoch+params.BeaconConfig().PersistentCommitteePeriod, ) } - domain := helpers.Domain(beaconState.Fork, exit.Epoch, params.BeaconConfig().DomainVoluntaryExit) + domain := helpers.Domain(fork, exit.Epoch, params.BeaconConfig().DomainVoluntaryExit) if err := verifySigningRoot(exit, validator.PublicKey, signed.Signature, domain); err != nil { return ErrSigFailedToVerify } diff --git a/beacon-chain/core/exit/BUILD.bazel b/beacon-chain/core/exit/BUILD.bazel deleted file mode 100644 index 04aaa63cf..000000000 --- a/beacon-chain/core/exit/BUILD.bazel +++ /dev/null @@ -1,37 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["validation.go"], - importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/exit", - visibility = [ - "//beacon-chain:__subpackages__", - ], - deps = [ - "//beacon-chain/core/helpers:go_default_library", - "//proto/beacon/p2p/v1:go_default_library", - "//shared/bls:go_default_library", - "//shared/mathutil:go_default_library", - "//shared/params:go_default_library", - "//shared/roughtime:go_default_library", - "@com_github_pkg_errors//:go_default_library", - "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", - "@com_github_prysmaticlabs_go_ssz//:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["validation_test.go"], - embed = [":go_default_library"], - deps = [ - "//beacon-chain/blockchain/testing:go_default_library", - "//beacon-chain/core/blocks:go_default_library", - "//beacon-chain/core/state:go_default_library", - "//beacon-chain/db/testing:go_default_library", - "//shared/params:go_default_library", - "//shared/testutil:go_default_library", - "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", - "@com_github_prysmaticlabs_go_ssz//:go_default_library", - ], -) diff --git a/beacon-chain/core/exit/validation.go b/beacon-chain/core/exit/validation.go deleted file mode 100644 index 1ed3e70c6..000000000 --- a/beacon-chain/core/exit/validation.go +++ /dev/null @@ -1,66 +0,0 @@ -package exit - -import ( - "fmt" - "time" - - "github.com/pkg/errors" - ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" - "github.com/prysmaticlabs/go-ssz" - "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" - pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" - "github.com/prysmaticlabs/prysm/shared/bls" - "github.com/prysmaticlabs/prysm/shared/mathutil" - "github.com/prysmaticlabs/prysm/shared/params" - "github.com/prysmaticlabs/prysm/shared/roughtime" -) - -// ValidateVoluntaryExit validates the voluntary exit. -// If it is invalid for some reason an error, if valid it will return no error. -func ValidateVoluntaryExit(state *pb.BeaconState, genesisTime time.Time, signed *ethpb.SignedVoluntaryExit) error { - if signed == nil || signed.Exit == nil { - return errors.New("nil signed voluntary exit") - } - ve := signed.Exit - if ve.ValidatorIndex >= uint64(len(state.Validators)) { - return fmt.Errorf("unknown validator index %d", ve.ValidatorIndex) - } - validator := state.Validators[ve.ValidatorIndex] - - if !helpers.IsActiveValidator(validator, ve.Epoch) { - return fmt.Errorf("validator %d not active at epoch %d", ve.ValidatorIndex, ve.Epoch) - } - if validator.ExitEpoch != params.BeaconConfig().FarFutureEpoch { - return fmt.Errorf("validator %d already exiting or exited", ve.ValidatorIndex) - } - - secondsPerEpoch := params.BeaconConfig().SecondsPerSlot * params.BeaconConfig().SlotsPerEpoch - currentEpoch := uint64(roughtime.Now().Unix()-genesisTime.Unix()) / secondsPerEpoch - earliestRequestedExitEpoch := mathutil.Max(ve.Epoch, currentEpoch) - earliestExitEpoch := validator.ActivationEpoch + params.BeaconConfig().PersistentCommitteePeriod - if earliestRequestedExitEpoch < earliestExitEpoch { - return fmt.Errorf("validator %d cannot exit before epoch %d", ve.ValidatorIndex, earliestExitEpoch) - } - - // Confirm signature is valid - root, err := ssz.HashTreeRoot(ve) - if err != nil { - return errors.Wrap(err, "cannot confirm signature") - } - sig, err := bls.SignatureFromBytes(signed.Signature) - if err != nil { - return errors.Wrap(err, "malformed signature") - } - validatorPubKey, err := bls.PublicKeyFromBytes(validator.PublicKey) - if err != nil { - return errors.Wrap(err, "invalid validator public key") - } - domain := bls.ComputeDomain(params.BeaconConfig().DomainVoluntaryExit) - verified := sig.Verify(root[:], validatorPubKey, domain) - if !verified { - return errors.New("incorrect signature") - } - - // Parameters are valid. - return nil -} diff --git a/beacon-chain/core/exit/validation_test.go b/beacon-chain/core/exit/validation_test.go deleted file mode 100644 index 3a592d636..000000000 --- a/beacon-chain/core/exit/validation_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package exit_test - -import ( - "context" - "errors" - "testing" - "time" - - ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" - "github.com/prysmaticlabs/go-ssz" - mockChain "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" - blk "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" - "github.com/prysmaticlabs/prysm/beacon-chain/core/exit" - "github.com/prysmaticlabs/prysm/beacon-chain/core/state" - dbutil "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" - "github.com/prysmaticlabs/prysm/shared/params" - "github.com/prysmaticlabs/prysm/shared/testutil" -) - -// Set genesis to a small set for faster test processing. -func init() { - p := params.BeaconConfig() - p.MinGenesisActiveValidatorCount = 8 - params.OverrideBeaconConfig(p) -} - -func TestValidation(t *testing.T) { - tests := []struct { - name string - epoch uint64 - validatorIndex uint64 - signature []byte - err error - }{ - { - name: "MissingValidator", - epoch: 2048, - validatorIndex: 16, - err: errors.New("unknown validator index 16"), - }, - { - name: "EarlyExit", - epoch: 2047, - validatorIndex: 0, - err: errors.New("validator 0 cannot exit before epoch 2048"), - }, - { - name: "NoSignature", - epoch: 2048, - validatorIndex: 0, - err: errors.New("malformed signature: signature must be 96 bytes"), - }, - { - name: "InvalidSignature", - epoch: 2048, - validatorIndex: 0, - signature: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - err: errors.New("malformed signature: could not unmarshal bytes into signature: err blsSignatureDeserialize 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), - }, - { - name: "IncorrectSignature", - epoch: 2048, - validatorIndex: 0, - signature: []byte{0xab, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, - err: errors.New("incorrect signature"), - }, - { - name: "Good", - epoch: 2048, - validatorIndex: 0, - signature: []byte{0xb3, 0xe1, 0x9d, 0xc6, 0x7c, 0x78, 0x6c, 0xcf, 0x33, 0x1d, 0xb9, 0x6f, 0x59, 0x64, 0x44, 0xe1, 0x29, 0xd0, 0x87, 0x03, 0x26, 0x6e, 0x49, 0x1c, 0x05, 0xae, 0x16, 0x7b, 0x04, 0x0f, 0x3f, 0xf8, 0x82, 0x77, 0x60, 0xfc, 0xcf, 0x2f, 0x59, 0xc7, 0x40, 0x0b, 0x2c, 0xa9, 0x23, 0x8a, 0x6c, 0x8d, 0x01, 0x21, 0x5e, 0xa8, 0xac, 0x36, 0x70, 0x31, 0xb0, 0xe1, 0xa8, 0xb8, 0x8f, 0x93, 0x8c, 0x1c, 0xa2, 0x86, 0xe7, 0x22, 0x00, 0x6a, 0x7d, 0x36, 0xc0, 0x2b, 0x86, 0x2c, 0xf5, 0xf9, 0x10, 0xb9, 0xf2, 0xbd, 0x5e, 0xa6, 0x5f, 0x12, 0x86, 0x43, 0x20, 0x4d, 0xa2, 0x9d, 0x8b, 0xe6, 0x6f, 0x09}, - }, - } - - db := dbutil.SetupDB(t) - defer dbutil.TeardownDB(t, db) - ctx := context.Background() - deposits, _, _ := testutil.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount) - beaconState, err := state.GenesisBeaconState(deposits, 0, ðpb.Eth1Data{BlockHash: make([]byte, 32)}) - if err != nil { - t.Fatal(err) - } - block := blk.NewGenesisBlock([]byte{}) - if err := db.SaveBlock(ctx, block); err != nil { - t.Fatalf("Could not save genesis block: %v", err) - } - genesisRoot, err := ssz.HashTreeRoot(block.Block) - if err != nil { - t.Fatalf("Could not get signing root %v", err) - } - - // Set genesis time to be 100 epochs ago - genesisTime := time.Now().Add(time.Duration(-100*int64(params.BeaconConfig().SecondsPerSlot*params.BeaconConfig().SlotsPerEpoch)) * time.Second) - mockChainService := &mockChain.ChainService{State: beaconState, Root: genesisRoot[:], Genesis: genesisTime} - headState, err := mockChainService.HeadState(context.Background()) - if err != nil { - t.Fatal("Failed to obtain head state") - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - req := ðpb.SignedVoluntaryExit{ - Exit: ðpb.VoluntaryExit{ - Epoch: test.epoch, - ValidatorIndex: test.validatorIndex, - }, - Signature: test.signature, - } - - err := exit.ValidateVoluntaryExit(headState, genesisTime, req) - if test.err == nil { - if err != nil { - t.Errorf("Unexpected error: received %v", err) - } - } else { - if err == nil { - t.Error("Failed to receive expected error") - } - if err.Error() != test.err.Error() { - t.Errorf("Unexpected error: expected %s, received %s", test.err.Error(), err.Error()) - } - } - }) - } -} diff --git a/beacon-chain/rpc/validator/BUILD.bazel b/beacon-chain/rpc/validator/BUILD.bazel index 170656597..2099f180d 100644 --- a/beacon-chain/rpc/validator/BUILD.bazel +++ b/beacon-chain/rpc/validator/BUILD.bazel @@ -17,7 +17,6 @@ go_library( "//beacon-chain/cache:go_default_library", "//beacon-chain/cache/depositcache:go_default_library", "//beacon-chain/core/blocks:go_default_library", - "//beacon-chain/core/exit:go_default_library", "//beacon-chain/core/feed:go_default_library", "//beacon-chain/core/feed/operation:go_default_library", "//beacon-chain/core/feed/state:go_default_library", diff --git a/beacon-chain/rpc/validator/exit.go b/beacon-chain/rpc/validator/exit.go index 95614f5f2..bba1e786e 100644 --- a/beacon-chain/rpc/validator/exit.go +++ b/beacon-chain/rpc/validator/exit.go @@ -5,23 +5,29 @@ import ( ptypes "github.com/gogo/protobuf/types" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" - "github.com/prysmaticlabs/prysm/beacon-chain/core/exit" + "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" opfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/operation" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // ProposeExit proposes an exit for a validator. func (vs *Server) ProposeExit(ctx context.Context, req *ethpb.SignedVoluntaryExit) (*ptypes.Empty, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "nil request") + } s, err := vs.HeadFetcher.HeadState(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err) } // Confirm the validator is eligible to exit with the parameters provided. - err = exit.ValidateVoluntaryExit(s, vs.GenesisTime, req) - if err != nil { + if int(req.Exit.ValidatorIndex) >= len(s.Validators) { + return nil, status.Error(codes.InvalidArgument, "validator index exceeds validator set length") + } + if err := blocks.VerifyExit(s.Validators[req.Exit.ValidatorIndex], helpers.StartSlot(req.Exit.Epoch), s.Fork, req); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } diff --git a/beacon-chain/sync/validate_voluntary_exit.go b/beacon-chain/sync/validate_voluntary_exit.go index e2559cf27..0c90d9b12 100644 --- a/beacon-chain/sync/validate_voluntary_exit.go +++ b/beacon-chain/sync/validate_voluntary_exit.go @@ -7,7 +7,6 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" - "github.com/prysmaticlabs/prysm/beacon-chain/core/state" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/traceutil" "go.opencensus.io/trace" @@ -42,22 +41,16 @@ func (r *Service) validateVoluntaryExit(ctx context.Context, pid peer.ID, msg *p return false } - // Retrieve head state, advance state to the epoch slot used specified in exit message. s, err := r.chain.HeadState(ctx) if err != nil { return false } exitedEpochSlot := exit.Exit.Epoch * params.BeaconConfig().SlotsPerEpoch - if s.Slot < exitedEpochSlot { - var err error - s, err = state.ProcessSlots(ctx, s, exitedEpochSlot) - if err != nil { - return false - } + if int(exit.Exit.ValidatorIndex) >= len(s.Validators) { + return false } - - if err := blocks.VerifyExit(s, exit); err != nil { + if err := blocks.VerifyExit(s.Validators[exit.Exit.ValidatorIndex], exitedEpochSlot, s.Fork, exit); err != nil { return false } diff --git a/beacon-chain/sync/validate_voluntary_exit_test.go b/beacon-chain/sync/validate_voluntary_exit_test.go index bd847da0f..25609b84a 100644 --- a/beacon-chain/sync/validate_voluntary_exit_test.go +++ b/beacon-chain/sync/validate_voluntary_exit_test.go @@ -25,7 +25,7 @@ func setupValidExit(t *testing.T) (*ethpb.SignedVoluntaryExit, *pb.BeaconState) exit := ðpb.SignedVoluntaryExit{ Exit: ðpb.VoluntaryExit{ ValidatorIndex: 0, - Epoch: 0, + Epoch: 1+params.BeaconConfig().PersistentCommitteePeriod, }, } registry := []*ethpb.Validator{