From dccf0992e58d28681a623250ba03ca9fc0c8780f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Mon, 19 Oct 2020 21:35:34 +0200 Subject: [PATCH] Verify eth1data vote in E2E (#7551) * add majority vote to e2e * extract policies to a separate package Co-authored-by: Raul Jordan Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> --- endtoend/evaluators/BUILD.bazel | 1 + endtoend/evaluators/finality.go | 3 +- endtoend/evaluators/metrics.go | 3 +- endtoend/evaluators/node.go | 15 +++---- endtoend/evaluators/operations.go | 75 +++++++++++++++++++++++++------ endtoend/evaluators/slashing.go | 15 ++++--- endtoend/evaluators/validator.go | 17 ++----- endtoend/minimal_e2e_test.go | 1 + endtoend/policies/BUILD.bazel | 8 ++++ endtoend/policies/policies.go | 27 +++++++++++ 10 files changed, 119 insertions(+), 46 deletions(-) create mode 100644 endtoend/policies/BUILD.bazel create mode 100644 endtoend/policies/policies.go diff --git a/endtoend/evaluators/BUILD.bazel b/endtoend/evaluators/BUILD.bazel index 7126ac7d07..0de7139046 100644 --- a/endtoend/evaluators/BUILD.bazel +++ b/endtoend/evaluators/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/p2p:go_default_library", "//endtoend/params:go_default_library", + "//endtoend/policies:go_default_library", "//endtoend/types:go_default_library", "//shared/bytesutil:go_default_library", "//shared/p2putils:go_default_library", diff --git a/endtoend/evaluators/finality.go b/endtoend/evaluators/finality.go index ccd689693e..57b954e9a9 100644 --- a/endtoend/evaluators/finality.go +++ b/endtoend/evaluators/finality.go @@ -7,6 +7,7 @@ import ( ptypes "github.com/gogo/protobuf/types" "github.com/pkg/errors" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/endtoend/policies" "github.com/prysmaticlabs/prysm/endtoend/types" "google.golang.org/grpc" ) @@ -15,7 +16,7 @@ import ( // Requires to be run after at least 4 epochs have passed. var FinalizationOccurs = types.Evaluator{ Name: "finalizes_at_epoch_%d", - Policy: afterNthEpoch(3), + Policy: policies.AfterNthEpoch(3), Evaluation: finalizationOccurs, } diff --git a/endtoend/evaluators/metrics.go b/endtoend/evaluators/metrics.go index 71cb2fd0e5..2d7fddc6eb 100644 --- a/endtoend/evaluators/metrics.go +++ b/endtoend/evaluators/metrics.go @@ -15,6 +15,7 @@ import ( eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/prysm/beacon-chain/p2p" e2e "github.com/prysmaticlabs/prysm/endtoend/params" + "github.com/prysmaticlabs/prysm/endtoend/policies" "github.com/prysmaticlabs/prysm/endtoend/types" "github.com/prysmaticlabs/prysm/shared/p2putils" "google.golang.org/grpc" @@ -26,7 +27,7 @@ const maxMemStatsBytes = 2000000000 // 2 GiB. // overall health is good. Not checking the first epoch so the sample size isn't too small. var MetricsCheck = types.Evaluator{ Name: "metrics_check_epoch_%d", - Policy: afterNthEpoch(0), + Policy: policies.AfterNthEpoch(0), Evaluation: metricsTest, } diff --git a/endtoend/evaluators/node.go b/endtoend/evaluators/node.go index 65cdd208e0..62e0c695fb 100644 --- a/endtoend/evaluators/node.go +++ b/endtoend/evaluators/node.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" e2e "github.com/prysmaticlabs/prysm/endtoend/params" + "github.com/prysmaticlabs/prysm/endtoend/policies" "github.com/prysmaticlabs/prysm/endtoend/types" "google.golang.org/grpc" ) @@ -24,21 +25,21 @@ var connTimeDelay = 50 * time.Millisecond // PeersConnect checks all beacon nodes and returns whether they are connected to each other as peers. var PeersConnect = types.Evaluator{ Name: "peers_connect_epoch_%d", - Policy: onEpoch(0), + Policy: policies.OnEpoch(0), Evaluation: peersConnect, } // HealthzCheck pings healthz and errors if it doesn't have the expected OK status. var HealthzCheck = types.Evaluator{ Name: "healthz_check_epoch_%d", - Policy: afterNthEpoch(0), + Policy: policies.AfterNthEpoch(0), Evaluation: healthzCheck, } // FinishedSyncing returns whether the beacon node with the given rpc port has finished syncing. var FinishedSyncing = types.Evaluator{ Name: "finished_syncing", - Policy: func(currentEpoch uint64) bool { return true }, + Policy: policies.AllEpochs, Evaluation: finishedSyncing, } @@ -46,16 +47,10 @@ var FinishedSyncing = types.Evaluator{ // Not checking head block root as it may change irregularly for the validator connected nodes. var AllNodesHaveSameHead = types.Evaluator{ Name: "all_nodes_have_same_head", - Policy: func(currentEpoch uint64) bool { return true }, + Policy: policies.AllEpochs, Evaluation: allNodesHaveSameHead, } -func onEpoch(epoch uint64) func(uint64) bool { - return func(currentEpoch uint64) bool { - return currentEpoch == epoch - } -} - func healthzCheck(conns ...*grpc.ClientConn) error { count := len(conns) for i := 0; i < count; i++ { diff --git a/endtoend/evaluators/operations.go b/endtoend/evaluators/operations.go index 89ce489088..d928eda1df 100644 --- a/endtoend/evaluators/operations.go +++ b/endtoend/evaluators/operations.go @@ -1,6 +1,7 @@ package evaluators import ( + "bytes" "context" "fmt" "math" @@ -8,8 +9,9 @@ import ( ptypes "github.com/gogo/protobuf/types" "github.com/pkg/errors" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" - "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + corehelpers "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" e2e "github.com/prysmaticlabs/prysm/endtoend/params" + "github.com/prysmaticlabs/prysm/endtoend/policies" "github.com/prysmaticlabs/prysm/endtoend/types" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" @@ -38,43 +40,43 @@ var depositEndEpoch = depositActivationStartEpoch + uint64(math.Ceil(float64(dep // ProcessesDepositsInBlocks ensures the expected amount of deposits are accepted into blocks. var ProcessesDepositsInBlocks = types.Evaluator{ Name: "processes_deposits_in_blocks_epoch_%d", - Policy: onEpoch(depositsInBlockStart), // We expect all deposits to enter in one epoch. + Policy: policies.OnEpoch(depositsInBlockStart), // We expect all deposits to enter in one epoch. Evaluation: processesDepositsInBlocks, } // ActivatesDepositedValidators ensures the expected amount of validator deposits are activated into the state. var ActivatesDepositedValidators = types.Evaluator{ Name: "processes_deposit_validators_epoch_%d", - Policy: isBetweenEpochs(depositActivationStartEpoch, depositEndEpoch), + Policy: policies.BetweenEpochs(depositActivationStartEpoch, depositEndEpoch), Evaluation: activatesDepositedValidators, } // DepositedValidatorsAreActive ensures the expected amount of validators are active after their deposits are processed. var DepositedValidatorsAreActive = types.Evaluator{ Name: "deposited_validators_are_active_epoch_%d", - Policy: afterNthEpoch(depositEndEpoch), + Policy: policies.AfterNthEpoch(depositEndEpoch), Evaluation: depositedValidatorsAreActive, } // ProposeVoluntaryExit sends a voluntary exit from randomly selected validator in the genesis set. var ProposeVoluntaryExit = types.Evaluator{ Name: "propose_voluntary_exit_epoch_%d", - Policy: onEpoch(7), + Policy: policies.OnEpoch(7), Evaluation: proposeVoluntaryExit, } // ValidatorHasExited checks the beacon state for the exited validator and ensures its marked as exited. var ValidatorHasExited = types.Evaluator{ Name: "voluntary_has_exited_%d", - Policy: onEpoch(8), + Policy: policies.OnEpoch(8), Evaluation: validatorIsExited, } -// Not including first epoch because of issues with genesis. -func isBetweenEpochs(fromEpoch, toEpoch uint64) func(uint64) bool { - return func(currentEpoch uint64) bool { - return fromEpoch < currentEpoch && currentEpoch < toEpoch - } +// ValidatorsVoteWithTheMajority verifies whether validator vote for eth1data using the majority algorithm. +var ValidatorsVoteWithTheMajority = types.Evaluator{ + Name: "validators_vote_with_the_majority_%d", + Policy: policies.AfterNthEpoch(0), + Evaluation: validatorsVoteWithTheMajority, } func processesDepositsInBlocks(conns ...*grpc.ClientConn) error { @@ -191,7 +193,7 @@ func depositedValidatorsAreActive(conns ...*grpc.ClientConn) error { inactiveCount, belowBalanceCount := 0, 0 for _, item := range validators.ValidatorList { - if !helpers.IsActiveValidator(item.Validator, chainHead.HeadEpoch) { + if !corehelpers.IsActiveValidator(item.Validator, chainHead.HeadEpoch) { inactiveCount++ } if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance { @@ -247,7 +249,7 @@ func proposeVoluntaryExit(conns ...*grpc.ClientConn) error { if err != nil { return err } - signingData, err := helpers.ComputeSigningRoot(voluntaryExit, domain.SignatureDomain) + signingData, err := corehelpers.ComputeSigningRoot(voluntaryExit, domain.SignatureDomain) if err != nil { return err } @@ -280,3 +282,50 @@ func validatorIsExited(conns ...*grpc.ClientConn) error { } return nil } + +func validatorsVoteWithTheMajority(conns ...*grpc.ClientConn) error { + conn := conns[0] + client := eth.NewBeaconChainClient(conn) + + chainHead, err := client.GetChainHead(context.Background(), &ptypes.Empty{}) + if err != nil { + return errors.Wrap(err, "failed to get chain head") + } + + req := ð.ListBlocksRequest{QueryFilter: ð.ListBlocksRequest_Epoch{Epoch: chainHead.HeadEpoch - 1}} + blks, err := client.ListBlocks(context.Background(), req) + if err != nil { + return errors.Wrap(err, "failed to get blocks from beacon-chain") + } + + for _, blk := range blks.BlockContainers { + slot, vote := blk.Block.Block.Slot, blk.Block.Block.Body.Eth1Data.BlockHash + slotsPerVotingPeriod := params.E2ETestConfig().SlotsPerEpoch * params.E2ETestConfig().EpochsPerEth1VotingPeriod + + // We treat epoch 1 differently from other epoch for two reasons: + // - this evaluator is not executed for epoch 0 so we have to calculate the first slot differently + // - for some reason the vote for the first slot in epoch 1 is 0x000... so we skip this slot + var isFirstSlotInVotingPeriod bool + if chainHead.HeadEpoch == 1 && slot%params.E2ETestConfig().SlotsPerEpoch == 0 { + continue + } + // We skipped the first slot so we treat the second slot as the starting slot of epoch 1. + if chainHead.HeadEpoch == 1 { + isFirstSlotInVotingPeriod = slot%params.E2ETestConfig().SlotsPerEpoch == 1 + } else { + isFirstSlotInVotingPeriod = slot%slotsPerVotingPeriod == 0 + } + if isFirstSlotInVotingPeriod { + expectedEth1DataVote = vote + return nil + } + + if !bytes.Equal(vote, expectedEth1DataVote) { + return fmt.Errorf("incorrect eth1data vote for slot %d; expected: %#x vs voted: %#x", + slot, expectedEth1DataVote, vote) + } + } + return nil +} + +var expectedEth1DataVote []byte diff --git a/endtoend/evaluators/slashing.go b/endtoend/evaluators/slashing.go index cca22688c5..63337a9e77 100644 --- a/endtoend/evaluators/slashing.go +++ b/endtoend/evaluators/slashing.go @@ -8,7 +8,8 @@ import ( "github.com/pkg/errors" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/go-bitfield" - "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + corehelpers "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/endtoend/policies" "github.com/prysmaticlabs/prysm/endtoend/types" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" @@ -20,28 +21,28 @@ import ( // InjectDoubleVote broadcasts a double vote into the beacon node pool for the slasher to detect. var InjectDoubleVote = types.Evaluator{ Name: "inject_double_vote_%d", - Policy: onEpoch(1), + Policy: policies.OnEpoch(1), Evaluation: insertDoubleAttestationIntoPool, } // ProposeDoubleBlock broadcasts a double block to the beacon node for the slasher to detect. var ProposeDoubleBlock = types.Evaluator{ Name: "propose_double_block_%d", - Policy: onEpoch(1), + Policy: policies.OnEpoch(1), Evaluation: proposeDoubleBlock, } // ValidatorsSlashed ensures the expected amount of validators are slashed. var ValidatorsSlashed = types.Evaluator{ Name: "validators_slashed_epoch_%d", - Policy: afterNthEpoch(1), + Policy: policies.AfterNthEpoch(1), Evaluation: validatorsSlashed, } // SlashedValidatorsLoseBalance checks if the validators slashed lose the right balance. var SlashedValidatorsLoseBalance = types.Evaluator{ Name: "slashed_validators_lose_valance_epoch_%d", - Policy: afterNthEpoch(1), + Policy: policies.AfterNthEpoch(1), Evaluation: validatorsLoseBalance, } @@ -150,7 +151,7 @@ func insertDoubleAttestationIntoPool(conns ...*grpc.ClientConn) error { if err != nil { return errors.Wrap(err, "could not get domain data") } - signingRoot, err := helpers.ComputeSigningRoot(attData, resp.SignatureDomain) + signingRoot, err := corehelpers.ComputeSigningRoot(attData, resp.SignatureDomain) if err != nil { return errors.Wrap(err, "could not compute signing root") } @@ -246,7 +247,7 @@ func proposeDoubleBlock(conns ...*grpc.ClientConn) error { if err != nil { return errors.Wrap(err, "could not get domain data") } - signingRoot, err := helpers.ComputeSigningRoot(blk, resp.SignatureDomain) + signingRoot, err := corehelpers.ComputeSigningRoot(blk, resp.SignatureDomain) if err != nil { return errors.Wrap(err, "could not compute signing root") } diff --git a/endtoend/evaluators/validator.go b/endtoend/evaluators/validator.go index 6d34bd7db4..95fc765a9c 100644 --- a/endtoend/evaluators/validator.go +++ b/endtoend/evaluators/validator.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/endtoend/policies" "github.com/prysmaticlabs/prysm/endtoend/types" "github.com/prysmaticlabs/prysm/shared/params" "google.golang.org/grpc" @@ -16,29 +17,17 @@ var expectedParticipation = 0.95 // 95% participation to make room for minor iss // ValidatorsAreActive ensures the expected amount of validators are active. var ValidatorsAreActive = types.Evaluator{ Name: "validators_active_epoch_%d", - Policy: allEpochs, + Policy: policies.AllEpochs, Evaluation: validatorsAreActive, } // ValidatorsParticipating ensures the expected amount of validators are active. var ValidatorsParticipating = types.Evaluator{ Name: "validators_participating_epoch_%d", - Policy: afterNthEpoch(2), + Policy: policies.AfterNthEpoch(2), Evaluation: validatorsParticipating, } -// Not including first epoch because of issues with genesis. -func afterNthEpoch(afterEpoch uint64) func(uint64) bool { - return func(currentEpoch uint64) bool { - return currentEpoch > afterEpoch - } -} - -// All epochs. -func allEpochs(_ uint64) bool { - return true -} - func validatorsAreActive(conns ...*grpc.ClientConn) error { conn := conns[0] client := eth.NewBeaconChainClient(conn) diff --git a/endtoend/minimal_e2e_test.go b/endtoend/minimal_e2e_test.go index a55efd5f1d..4cd8ac1164 100644 --- a/endtoend/minimal_e2e_test.go +++ b/endtoend/minimal_e2e_test.go @@ -50,6 +50,7 @@ func TestEndToEnd_MinimalConfig(t *testing.T) { ev.DepositedValidatorsAreActive, ev.ProposeVoluntaryExit, ev.ValidatorHasExited, + ev.ValidatorsVoteWithTheMajority, ev.ColdStateCheckpoint, }, } diff --git a/endtoend/policies/BUILD.bazel b/endtoend/policies/BUILD.bazel new file mode 100644 index 0000000000..3632040e9e --- /dev/null +++ b/endtoend/policies/BUILD.bazel @@ -0,0 +1,8 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["policies.go"], + importpath = "github.com/prysmaticlabs/prysm/endtoend/policies", + visibility = ["//visibility:public"], +) diff --git a/endtoend/policies/policies.go b/endtoend/policies/policies.go new file mode 100644 index 0000000000..8252444fee --- /dev/null +++ b/endtoend/policies/policies.go @@ -0,0 +1,27 @@ +package policies + +// AfterNthEpoch runs for every epoch after the provided epoch. +func AfterNthEpoch(afterEpoch uint64) func(uint64) bool { + return func(currentEpoch uint64) bool { + return currentEpoch > afterEpoch + } +} + +// AllEpochs runs for all epochs. +func AllEpochs(_ uint64) bool { + return true +} + +// OnEpoch runs only for the provided epoch. +func OnEpoch(epoch uint64) func(uint64) bool { + return func(currentEpoch uint64) bool { + return currentEpoch == epoch + } +} + +// BetweenEpochs runs for every epoch that is between the provided epochs. +func BetweenEpochs(fromEpoch, toEpoch uint64) func(uint64) bool { + return func(currentEpoch uint64) bool { + return fromEpoch < currentEpoch && currentEpoch < toEpoch + } +}