Refactor block operations for validating exits slightly (#4612)

* Refactor block operations for validating exits slightly so that we don't have to advance state in a pubsub validator

* current slot

* remove duplicated validation for exits

* nil request check

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
Preston Van Loon 2020-01-22 13:49:38 -08:00 committed by GitHub
parent 9a395530b7
commit cc58b5aca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 20 additions and 251 deletions

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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, &ethpb.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 := &ethpb.SignedVoluntaryExit{
Exit: &ethpb.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())
}
}
})
}
}

View File

@ -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",

View File

@ -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())
}

View File

@ -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
}

View File

@ -25,7 +25,7 @@ func setupValidExit(t *testing.T) (*ethpb.SignedVoluntaryExit, *pb.BeaconState)
exit := &ethpb.SignedVoluntaryExit{
Exit: &ethpb.VoluntaryExit{
ValidatorIndex: 0,
Epoch: 0,
Epoch: 1+params.BeaconConfig().PersistentCommitteePeriod,
},
}
registry := []*ethpb.Validator{