Changes to E2E for Optimized Slasher (#9698)

* remaining slasher e2e changes

* testing gaz

* slash e2e

* deepsource

* gaz

* viz

* revert wait group changes

* comment

* lock around reset cache

* add slashing reason

* revert changes

* is sync

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
Co-authored-by: prestonvanloon <preston@prysmaticlabs.com>
This commit is contained in:
Raul Jordan
2021-09-30 15:28:14 -05:00
committed by GitHub
parent 2bc3f4bc6a
commit 4f31ba6489
15 changed files with 177 additions and 76 deletions

View File

@@ -131,6 +131,7 @@ func (p *Pool) InsertAttesterSlashing(
slashedVal := slice.IntersectionUint64(slashing.Attestation_1.AttestingIndices, slashing.Attestation_2.AttestingIndices)
cantSlash := make([]uint64, 0, len(slashedVal))
slashingReason := ""
for _, val := range slashedVal {
// Has this validator index been included recently?
ok, err := p.validatorSlashingPreconditionCheck(state, types.ValidatorIndex(val))
@@ -140,6 +141,7 @@ func (p *Pool) InsertAttesterSlashing(
// If the validator has already exited, has already been slashed, or if its index
// has been recently included in the pool of slashings, skip including this indice.
if !ok {
slashingReason = "validator already exited/slashed or already recently included in slashings pool"
cantSlash = append(cantSlash, val)
continue
}
@@ -150,6 +152,7 @@ func (p *Pool) InsertAttesterSlashing(
return uint64(p.pendingAttesterSlashing[i].validatorToSlash) >= val
})
if found != len(p.pendingAttesterSlashing) && uint64(p.pendingAttesterSlashing[found].validatorToSlash) == val {
slashingReason = "validator already exist in list of pending slashings, no need to attempt to slash again"
cantSlash = append(cantSlash, val)
continue
}
@@ -166,7 +169,11 @@ func (p *Pool) InsertAttesterSlashing(
numPendingAttesterSlashings.Set(float64(len(p.pendingAttesterSlashing)))
}
if len(cantSlash) == len(slashedVal) {
return fmt.Errorf("could not slash any of %d validators in submitted slashing", len(slashedVal))
return fmt.Errorf(
"could not slash any of %d validators in submitted slashing: %s",
len(slashedVal),
slashingReason,
)
}
return nil
}

View File

@@ -10,6 +10,7 @@ go_test(
"endtoend_test.go",
"minimal_e2e_test.go",
"minimal_slashing_e2e_test.go",
"slasher_simulator_e2e_test.go",
],
args = ["-test.v"],
data = [
@@ -28,8 +29,13 @@ go_test(
"requires-network",
],
deps = [
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/core/transition:go_default_library",
"//beacon-chain/db/testing:go_default_library",
"//beacon-chain/operations/slashings:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//config/params:go_default_library",
"//crypto/bls:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/endtoend/components:go_default_library",
@@ -38,9 +44,12 @@ go_test(
"//testing/endtoend/params:go_default_library",
"//testing/endtoend/types:go_default_library",
"//testing/require:go_default_library",
"//testing/slasher/simulator:go_default_library",
"//testing/util:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
"@org_golang_x_sync//errgroup:go_default_library",

View File

@@ -140,7 +140,6 @@ func (r *testRunner) run() {
requiredComponents := []e2etypes.ComponentRunner{
tracingSink, eth1Node, bootNode, beaconNodes, validatorNodes,
}
ctxAllNodesReady, cancel := context.WithTimeout(ctx, allNodesStartTimeout)
defer cancel()
if err := helpers.ComponentsStarted(ctxAllNodesReady, requiredComponents); err != nil {
@@ -148,7 +147,7 @@ func (r *testRunner) run() {
}
// Since defer unwraps in LIFO order, parent context will be closed only after logs are written.
defer helpers.LogOutput(t, config)
defer helpers.LogOutput(t)
if config.UsePprof {
defer func() {
log.Info("Writing output pprof files")
@@ -228,26 +227,23 @@ func (r *testRunner) runEvaluators(conns []*grpc.ClientConn, tickingStartTime ti
ticker := helpers.NewEpochTicker(tickingStartTime, secondsPerEpoch)
for currentEpoch := range ticker.C() {
wg := new(sync.WaitGroup)
for _, ev := range config.Evaluators {
for _, eval := range config.Evaluators {
// Fix reference to evaluator as it will be running
// in a separate goroutine.
evaluator := ev
evaluator := eval
// Only run if the policy says so.
if !evaluator.Policy(types.Epoch(currentEpoch)) {
continue
}
// Add evaluator to our waitgroup.
wg.Add(1)
go t.Run(fmt.Sprintf(evaluator.Name, currentEpoch), func(t *testing.T) {
err := evaluator.Evaluation(conns...)
assert.NoError(t, err, "Evaluation failed for epoch %d: %v", currentEpoch, err)
wg.Done()
})
}
// Wait for all evaluators to finish their evaluation for the epoch.
wg.Wait()
if t.Failed() || currentEpoch >= config.EpochsToRun-1 {
ticker.Done()
if t.Failed() {
@@ -319,7 +315,6 @@ func (r *testRunner) testBeaconChainSync(ctx context.Context, g *errgroup.Group,
assert.NoError(t, evaluator.Evaluation(conns...), "Evaluation failed for sync node")
})
}
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/pkg/errors"
ethtypes "github.com/prysmaticlabs/eth2-types"
eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/testing/endtoend/policies"
"github.com/prysmaticlabs/prysm/testing/endtoend/types"
@@ -14,10 +15,12 @@ import (
// FinalizationOccurs is an evaluator to make sure finalization is performing as it should.
// Requires to be run after at least 4 epochs have passed.
var FinalizationOccurs = types.Evaluator{
Name: "finalizes_at_epoch_%d",
Policy: policies.AfterNthEpoch(3),
Evaluation: finalizationOccurs,
var FinalizationOccurs = func(epoch ethtypes.Epoch) types.Evaluator {
return types.Evaluator{
Name: "finalizes_at_epoch_%d",
Policy: policies.AfterNthEpoch(epoch),
Evaluation: finalizationOccurs,
}
}
func finalizationOccurs(conns ...*grpc.ClientConn) error {

View File

@@ -39,7 +39,7 @@ var HealthzCheck = e2etypes.Evaluator{
// FinishedSyncing returns whether the beacon node with the given rpc port has finished syncing.
var FinishedSyncing = e2etypes.Evaluator{
Name: "finished_syncing",
Name: "finished_syncing_%d",
Policy: policies.AllEpochs,
Evaluation: finishedSyncing,
}
@@ -47,7 +47,7 @@ var FinishedSyncing = e2etypes.Evaluator{
// AllNodesHaveSameHead ensures all nodes have the same head epoch. Checks finality and justification as well.
// Not checking head block root as it may change irregularly for the validator connected nodes.
var AllNodesHaveSameHead = e2etypes.Evaluator{
Name: "all_nodes_have_same_head",
Name: "all_nodes_have_same_head_%d",
Policy: policies.AllEpochs,
Evaluation: allNodesHaveSameHead,
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/prysmaticlabs/prysm/container/slice"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
e2e "github.com/prysmaticlabs/prysm/testing/endtoend/params"
"github.com/prysmaticlabs/prysm/testing/endtoend/policies"
e2eTypes "github.com/prysmaticlabs/prysm/testing/endtoend/types"
"github.com/prysmaticlabs/prysm/testing/util"
@@ -19,32 +20,40 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
)
// InjectDoubleVote broadcasts a double vote into the beacon node pool for the slasher to detect.
var InjectDoubleVote = e2eTypes.Evaluator{
Name: "inject_double_vote_%d",
Policy: policies.OnEpoch(1),
Evaluation: insertDoubleAttestationIntoPool,
// InjectDoubleVoteOnEpoch broadcasts a double vote into the beacon node pool for the slasher to detect.
var InjectDoubleVoteOnEpoch = func(n types.Epoch) e2eTypes.Evaluator {
return e2eTypes.Evaluator{
Name: "inject_double_vote_%d",
Policy: policies.OnEpoch(n),
Evaluation: insertDoubleAttestationIntoPool,
}
}
// ProposeDoubleBlock broadcasts a double block to the beacon node for the slasher to detect.
var ProposeDoubleBlock = e2eTypes.Evaluator{
Name: "propose_double_block_%d",
Policy: policies.OnEpoch(1),
Evaluation: proposeDoubleBlock,
// InjectDoubleBlockOnEpoch proposes a double block to the beacon node for the slasher to detect.
var InjectDoubleBlockOnEpoch = func(n types.Epoch) e2eTypes.Evaluator {
return e2eTypes.Evaluator{
Name: "inject_double_block_%d",
Policy: policies.OnEpoch(n),
Evaluation: proposeDoubleBlock,
}
}
// ValidatorsSlashed ensures the expected amount of validators are slashed.
var ValidatorsSlashed = e2eTypes.Evaluator{
Name: "validators_slashed_epoch_%d",
Policy: policies.AfterNthEpoch(1),
Evaluation: validatorsSlashed,
// ValidatorsSlashedAfterEpoch ensures the expected amount of validators are slashed.
var ValidatorsSlashedAfterEpoch = func(n types.Epoch) e2eTypes.Evaluator {
return e2eTypes.Evaluator{
Name: "validators_slashed_epoch_%d",
Policy: policies.AfterNthEpoch(n),
Evaluation: validatorsSlashed,
}
}
// SlashedValidatorsLoseBalance checks if the validators slashed lose the right balance.
var SlashedValidatorsLoseBalance = e2eTypes.Evaluator{
Name: "slashed_validators_lose_valance_epoch_%d",
Policy: policies.AfterNthEpoch(1),
Evaluation: validatorsLoseBalance,
// SlashedValidatorsLoseBalanceAfterEpoch checks if the validators slashed lose the right balance.
var SlashedValidatorsLoseBalanceAfterEpoch = func(n types.Epoch) e2eTypes.Evaluator {
return e2eTypes.Evaluator{
Name: "slashed_validators_lose_valance_epoch_%d",
Policy: policies.AfterNthEpoch(n),
Evaluation: validatorsLoseBalance,
}
}
var slashedIndices []uint64
@@ -193,7 +202,6 @@ func proposeDoubleBlock(conns ...*grpc.ClientConn) error {
if err != nil {
return errors.Wrap(err, "could not get chain head")
}
_, privKeys, err := util.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount)
if err != nil {
return err
@@ -218,10 +226,23 @@ func proposeDoubleBlock(conns ...*grpc.ClientConn) error {
}
}
validatorNum := int(params.BeaconConfig().MinGenesisActiveValidatorCount)
beaconNodeNum := e2e.TestParams.BeaconNodeCount
if validatorNum%beaconNodeNum != 0 {
return errors.New("validator count is not easily divisible by beacon node count")
}
validatorsPerNode := validatorNum / beaconNodeNum
// If the proposer index is in the second validator client, we connect to
// the corresponding beacon node instead.
if proposerIndex >= types.ValidatorIndex(uint64(validatorsPerNode)) {
valClient = eth.NewBeaconNodeValidatorClient(conns[1])
}
hashLen := 32
blk := &eth.BeaconBlock{
Slot: chainHead.HeadSlot - 1,
ParentRoot: bytesutil.PadTo([]byte("bad parent root"), hashLen),
Slot: chainHead.HeadSlot + 1,
ParentRoot: chainHead.HeadBlockRoot,
StateRoot: bytesutil.PadTo([]byte("bad state root"), hashLen),
ProposerIndex: proposerIndex,
Body: &eth.BeaconBlockBody{
@@ -260,8 +281,7 @@ func proposeDoubleBlock(conns ...*grpc.ClientConn) error {
// We only broadcast to conns[0] here since we can trust that at least 1 node will be online.
// Only broadcasting the attestation to one node also helps test slashing propagation.
client := eth.NewBeaconNodeValidatorClient(conns[0])
if _, err = client.ProposeBlock(ctx, signedBlk); err == nil {
if _, err = valClient.ProposeBlock(ctx, signedBlk); err == nil {
return errors.New("expected block to fail processing")
}
slashedIndices = append(slashedIndices, uint64(proposerIndex))

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/pkg/errors"
ethtypes "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/config/params"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
@@ -27,10 +28,12 @@ var ValidatorsAreActive = types.Evaluator{
}
// ValidatorsParticipating ensures the expected amount of validators are active.
var ValidatorsParticipating = types.Evaluator{
Name: "validators_participating_epoch_%d",
Policy: policies.AfterNthEpoch(2),
Evaluation: validatorsParticipating,
var ValidatorsParticipatingAtEpoch = func(epoch ethtypes.Epoch) types.Evaluator {
return types.Evaluator{
Name: "validators_participating_epoch_%d",
Policy: policies.AfterNthEpoch(epoch),
Evaluation: validatorsParticipating,
}
}
// ValidatorSyncParticipation ensures the expected amount of sync committee participants

View File

@@ -109,7 +109,7 @@ random:
}
// LogOutput logs the output of all log files made.
func LogOutput(t *testing.T, config *e2etypes.E2EConfig) {
func LogOutput(t *testing.T) {
// Log out errors from beacon chain nodes.
for i := 0; i < e2e.TestParams.BeaconNodeCount; i++ {
beaconLogFile, err := os.Open(path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.BeaconNodeLogFileName, i)))
@@ -123,15 +123,8 @@ func LogOutput(t *testing.T, config *e2etypes.E2EConfig) {
t.Fatal(err)
}
LogErrorOutput(t, validatorLogFile, "validator client", i)
if config.TestSlasher {
slasherLogFile, err := os.Open(path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.SlasherLogFileName, i)))
if err != nil {
t.Fatal(err)
}
LogErrorOutput(t, slasherLogFile, "slasher client", i)
}
}
t.Logf("Ending time: %s\n", time.Now().String())
}

View File

@@ -47,8 +47,8 @@ func e2eMinimal(t *testing.T, usePrysmSh bool) {
ev.HealthzCheck,
ev.MetricsCheck,
ev.ValidatorsAreActive,
ev.ValidatorsParticipating,
ev.FinalizationOccurs,
ev.ValidatorsParticipatingAtEpoch(2),
ev.FinalizationOccurs(3),
ev.ProcessesDepositsInBlocks,
ev.VerifyBlockGraffiti,
ev.ActivatesDepositedValidators,
@@ -60,6 +60,8 @@ func e2eMinimal(t *testing.T, usePrysmSh bool) {
ev.ForkTransition,
ev.APIMiddlewareVerifyIntegrity,
ev.APIGatewayV1Alpha1VerifyIntegrity,
ev.FinishedSyncing,
ev.AllNodesHaveSameHead,
}
// TODO(#9166): remove this block once v2 changes are live.
if !usePrysmSh {
@@ -78,7 +80,6 @@ func e2eMinimal(t *testing.T, usePrysmSh bool) {
EpochsToRun: uint64(epochsToRun),
TestSync: true,
TestDeposits: true,
TestSlasher: false,
UsePrysmShValidator: usePrysmSh,
UsePprof: !longRunning,
TracingSinkEndpoint: tracingEndpoint,

View File

@@ -10,26 +10,25 @@ import (
"github.com/prysmaticlabs/prysm/testing/require"
)
func TestEndToEnd_Slashing_MinimalConfig(t *testing.T) {
t.Skip("To be replaced with the new slasher implementation")
func TestEndToEnd_Slasher_MinimalConfig(t *testing.T) {
params.UseE2EConfig()
require.NoError(t, e2eParams.Init(e2eParams.StandardBeaconCount))
testConfig := &types.E2EConfig{
BeaconFlags: []string{},
BeaconFlags: []string{
"--slasher",
},
ValidatorFlags: []string{},
EpochsToRun: 4,
TestSync: false,
TestSlasher: true,
TestDeposits: false,
Evaluators: []types.Evaluator{
ev.PeersConnect,
ev.HealthzCheck,
ev.ValidatorsSlashed,
ev.SlashedValidatorsLoseBalance,
ev.InjectDoubleVote,
ev.ProposeDoubleBlock,
ev.ValidatorsSlashedAfterEpoch(4),
ev.SlashedValidatorsLoseBalanceAfterEpoch(4),
ev.InjectDoubleVoteOnEpoch(2),
ev.InjectDoubleBlockOnEpoch(2),
},
}

View File

@@ -26,8 +26,6 @@ type params struct {
BeaconNodeMetricsPort int
ValidatorMetricsPort int
ValidatorGatewayPort int
SlasherRPCPort int
SlasherMetricsPort int
}
// TestParams is the globally accessible var for getting config elements.
@@ -42,9 +40,6 @@ var TracingRequestSinkFileName = "tracing-http-requests.log.gz"
// BeaconNodeLogFileName is the file name used for the beacon chain node logs.
var BeaconNodeLogFileName = "beacon-%d.log"
// SlasherLogFileName is the file name used for the slasher client logs.
var SlasherLogFileName = "slasher-%d.log"
// ValidatorLogFileName is the file name used for the validator client logs.
var ValidatorLogFileName = "vals-%d.log"
@@ -82,8 +77,6 @@ func Init(beaconNodeCount int) error {
BeaconNodeMetricsPort: 5100 + testIndex*100,
ValidatorMetricsPort: 6100 + testIndex*100,
ValidatorGatewayPort: 7150 + testIndex*100,
SlasherRPCPort: 7100 + testIndex*100,
SlasherMetricsPort: 8100 + testIndex*100,
}
return nil
}

View File

@@ -0,0 +1,78 @@
package endtoend
import (
"context"
"os"
"strconv"
"testing"
types "github.com/prysmaticlabs/eth2-types"
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/crypto/bls"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/testing/require"
slashersimulator "github.com/prysmaticlabs/prysm/testing/slasher/simulator"
"github.com/prysmaticlabs/prysm/testing/util"
logTest "github.com/sirupsen/logrus/hooks/test"
)
func TestEndToEnd_SlasherSimulator(t *testing.T) {
hook := logTest.NewGlobal()
ctx := context.Background()
// Run for 10 epochs if not in long-running to confirm long-running has no issues.
simulatorParams := slashersimulator.DefaultParams()
var err error
epochStr, longRunning := os.LookupEnv("E2E_EPOCHS")
if longRunning {
epochsToRun, err := strconv.Atoi(epochStr)
require.NoError(t, err)
simulatorParams.NumEpochs = uint64(epochsToRun)
}
slasherDB := dbtest.SetupSlasherDB(t)
beaconState, err := util.NewBeaconState()
require.NoError(t, err)
// We setup validators in the beacon state along with their
// private keys used to generate valid signatures in generated objects.
validators := make([]*ethpb.Validator, simulatorParams.NumValidators)
privKeys := make(map[types.ValidatorIndex]bls.SecretKey)
for valIdx := range validators {
privKey, err := bls.RandKey()
require.NoError(t, err)
privKeys[types.ValidatorIndex(valIdx)] = privKey
validators[valIdx] = &ethpb.Validator{
PublicKey: privKey.PublicKey().Marshal(),
WithdrawalCredentials: make([]byte, 32),
}
}
err = beaconState.SetValidators(validators)
require.NoError(t, err)
mockChain := &mock.ChainService{State: beaconState}
gen := stategen.NewMockService()
gen.AddStateForRoot(beaconState, [32]byte{})
sim, err := slashersimulator.New(ctx, &slashersimulator.ServiceConfig{
Params: simulatorParams,
Database: slasherDB,
StateNotifier: &mock.MockStateNotifier{},
HeadStateFetcher: mockChain,
AttestationStateFetcher: mockChain,
StateGen: gen,
PrivateKeysByValidatorIndex: privKeys,
SlashingsPool: &slashings.PoolMock{},
})
require.NoError(t, err)
sim.Start()
err = sim.Stop()
require.NoError(t, err)
require.LogsDoNotContain(t, hook, "ERROR")
require.LogsDoNotContain(t, hook, "Did not detect")
require.LogsContain(t, hook, "Correctly detected simulated proposer slashing")
require.LogsContain(t, hook, "Correctly detected simulated attester slashing")
}

View File

@@ -15,7 +15,6 @@ type E2EConfig struct {
UsePrysmShValidator bool
UsePprof bool
TestDeposits bool
TestSlasher bool
EpochsToRun uint64
TracingSinkEndpoint string
Evaluators []Evaluator

View File

@@ -10,7 +10,7 @@ go_library(
],
importpath = "github.com/prysmaticlabs/prysm/testing/slasher/simulator",
visibility = [
"//endtoend:__subpackages__",
"//testing/endtoend:__subpackages__",
],
deps = [
"//async/event:go_default_library",

View File

@@ -247,7 +247,6 @@ func DeterministicEth1Data(size int) (*ethpb.Eth1Data, error) {
// DeterministicGenesisState returns a genesis state made using the deterministic deposits.
func DeterministicGenesisState(t testing.TB, numValidators uint64) (state.BeaconState, []bls.SecretKey) {
resetCache()
deposits, privKeys, err := DeterministicDepositsAndKeys(numValidators)
if err != nil {
t.Fatal(errors.Wrapf(err, "failed to get %d deposits", numValidators))
@@ -290,6 +289,8 @@ func DepositTrieFromDeposits(deposits []*ethpb.Deposit) (*trie.SparseMerkleTrie,
// resetCache clears out the old trie, private keys and deposits.
func resetCache() {
lock.Lock()
defer lock.Unlock()
t = nil
privKeys = []bls.SecretKey{}
cachedDeposits = []*ethpb.Deposit{}