prysm-pulse/validator/client/validator_propose_test.go
shayzluf 0732012459 Validator-multiple key (#2069)
* first version - broken

* working proto changes

* resolve review remarks

* fix goimport issues

* fix service issues

* first logic version-broken

* first running version - no new tests

* fix validator client test

* add wait group to goroutines

* remove unused var in function call

* fix review remarks and tests

* merge master changes and fix conflicts

* gazzele fix

* fix prestonvanloon requested changes

* merge and some of terenc3t remarks addressed

* _,pk bug fix in log

* fix account file name suffix and filter not active validator out

* merge with master and fix missing parameters

* run over all public keys in hasvalidators

* add test for error when no all the validators has index in the db and hasvalidators is called

* fix runner tests fail due to timing issues

* goimports

* smaller sleep time in proposer tests

* fix UpdateAssignments loging

* fix goimports

* added && false commented TestUpdateAssignments_DoesNothingWhenNotEpochStartAndAlreadyExistingAssignments

* hasvalidators without missing publickeys list

* fix some of prestone review remarks

* fixes for prestone comments

* review changes applied

* expect context call in TestWaitForActivation_ValidatorOriginallyExists

* changed hasvalidators to return true if one validator exists

* fix init problem to getkeys

* hasvalidators requiers all validators to be in db

* validator attest assignments update

* fix ap var name

* Change name to hasallvalidators

* fix tests

* update script, fix any vs all validator calls

* fix wait for activation

* filter validator

* reuse the reply block

* fix imports

* Remove dup

* better lookup of active validators

* better filter active vlaidators, still need to fix committee assignment tests

* lint

* use activated keys

* fix for postchainstart

* fix logging

* move state transitions

* hasanyvalidator and hasallvalidators

* fix tests with updatechainhead missing

* add tests

* fix TestCommitteeAssignment_OK

* fix test

* fix validator tests

* fix TestCommitteeAssignment_multipleKeys_OK and TestWaitForActivation_ValidatorOriginallyExists

* fix goimports

* removed unused param from assignment

* change string(pk) to hex.EncodeString(pk) fix change requests

* add inactive validator status to assignments

* fix logging mess due to multi validator setup

* set no assignment to debug level

* log assignments every epoch

* logging fixes

* fixed runtime by using the right assignments

* correct activation request

* fix the validator panic

* correct assignment

* fix test fail and waitforactivation

* performance log issue fix

* fix goimports

* add log message with truncated pk for attest

* add truncated pk to attest and propose logs

* Add comment to script, change 9 to 8

* Update assignment log

* Add comment, report number of assignments

* Use WithError, add validator as field, merge block proposal log

* Update validator_propose.go

* fix

* use entry.String()

* fix fmt
2019-04-18 12:23:38 -05:00

481 lines
15 KiB
Go

package client
import (
"bytes"
"context"
"encoding/hex"
"errors"
"testing"
ptypes "github.com/gogo/protobuf/types"
"github.com/golang/mock/gomock"
pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/validator/internal"
logTest "github.com/sirupsen/logrus/hooks/test"
)
type mocks struct {
proposerClient *internal.MockProposerServiceClient
beaconClient *internal.MockBeaconServiceClient
validatorClient *internal.MockValidatorServiceClient
attesterClient *internal.MockAttesterServiceClient
}
func setup(t *testing.T) (*validator, *mocks, func()) {
ctrl := gomock.NewController(t)
m := &mocks{
proposerClient: internal.NewMockProposerServiceClient(ctrl),
beaconClient: internal.NewMockBeaconServiceClient(ctrl),
validatorClient: internal.NewMockValidatorServiceClient(ctrl),
attesterClient: internal.NewMockAttesterServiceClient(ctrl),
}
validator := &validator{
proposerClient: m.proposerClient,
beaconClient: m.beaconClient,
attesterClient: m.attesterClient,
validatorClient: m.validatorClient,
keys: keyMap,
}
return validator, m, ctrl.Finish
}
func TestProposeBlock_DoesNotProposeGenesisBlock(t *testing.T) {
hook := logTest.NewGlobal()
validator, _, finish := setup(t)
defer finish()
validator.ProposeBlock(context.Background(), params.BeaconConfig().GenesisSlot, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
testutil.AssertLogsContain(t, hook, "Assigned to genesis slot, skipping proposal")
}
func TestProposeBlock_LogsCanonicalHeadFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(nil /*beaconBlock*/, errors.New("something bad happened"))
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
testutil.AssertLogsContain(t, hook, "something bad happened")
}
func TestProposeBlock_PendingDepositsFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(nil /*response*/, errors.New("something bad happened"))
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
testutil.AssertLogsContain(t, hook, "something bad happened")
}
func TestProposeBlock_UsePendingDeposits(t *testing.T) {
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{
PendingDeposits: []*pbp2p.Deposit{
{DepositData: []byte{'D', 'A', 'T', 'A'}},
},
}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.Eth1DataResponse{}, nil /*err*/)
m.beaconClient.EXPECT().ForkData(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.Fork{
Epoch: params.BeaconConfig().GenesisEpoch,
CurrentVersion: 0,
PreviousVersion: 0,
}, nil /*err*/)
m.proposerClient.EXPECT().PendingAttestations(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}),
).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil)
m.proposerClient.EXPECT().ComputeStateRoot(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(&pb.StateRootResponse{
StateRoot: []byte{'F'},
}, nil /*err*/)
var broadcastedBlock *pbp2p.BeaconBlock
m.proposerClient.EXPECT().ProposeBlock(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Do(func(_ context.Context, blk *pbp2p.BeaconBlock) {
broadcastedBlock = blk
}).Return(&pb.ProposeResponse{}, nil /*error*/)
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
if !bytes.Equal(broadcastedBlock.Body.Deposits[0].DepositData, []byte{'D', 'A', 'T', 'A'}) {
t.Errorf("Unexpected deposit data: %v", broadcastedBlock.Body.Deposits)
}
}
func TestProposeBlock_Eth1DataFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(nil /*response*/, errors.New("something bad happened"))
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
testutil.AssertLogsContain(t, hook, "something bad happened")
}
func TestProposeBlock_UsesEth1Data(t *testing.T) {
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.Eth1DataResponse{
Eth1Data: &pbp2p.Eth1Data{BlockHash32: []byte{'B', 'L', 'O', 'C', 'K'}},
}, nil /*err*/)
m.beaconClient.EXPECT().ForkData(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.Fork{
Epoch: params.BeaconConfig().GenesisEpoch,
CurrentVersion: 0,
PreviousVersion: 0,
}, nil /*err*/)
m.proposerClient.EXPECT().PendingAttestations(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}),
).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil)
m.proposerClient.EXPECT().ComputeStateRoot(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(&pb.StateRootResponse{
StateRoot: []byte{'F'},
}, nil /*err*/)
var broadcastedBlock *pbp2p.BeaconBlock
m.proposerClient.EXPECT().ProposeBlock(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Do(func(_ context.Context, blk *pbp2p.BeaconBlock) {
broadcastedBlock = blk
}).Return(&pb.ProposeResponse{}, nil /*error*/)
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
if !bytes.Equal(broadcastedBlock.Eth1Data.BlockHash32, []byte{'B', 'L', 'O', 'C', 'K'}) {
t.Errorf("Unexpected ETH1 data: %v", broadcastedBlock.Eth1Data)
}
}
func TestProposeBlock_PendingAttestations_UsesCurrentSlot(t *testing.T) {
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.Eth1DataResponse{
Eth1Data: &pbp2p.Eth1Data{BlockHash32: []byte{'B', 'L', 'O', 'C', 'K'}},
}, nil /*err*/)
m.beaconClient.EXPECT().ForkData(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.Fork{
Epoch: params.BeaconConfig().GenesisEpoch,
CurrentVersion: 0,
PreviousVersion: 0,
}, nil /*err*/)
var req *pb.PendingAttestationsRequest
m.proposerClient.EXPECT().PendingAttestations(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}),
).DoAndReturn(func(_ context.Context, r *pb.PendingAttestationsRequest) (*pb.PendingAttestationsResponse, error) {
req = r
return &pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil
})
m.proposerClient.EXPECT().ComputeStateRoot(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(&pb.StateRootResponse{
StateRoot: []byte{'F'},
}, nil /*err*/)
m.proposerClient.EXPECT().ProposeBlock(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(&pb.ProposeResponse{}, nil /*error*/)
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
if req.ProposalBlockSlot != 55 {
t.Errorf(
"expected request to use the current proposal slot %d, but got %d",
55,
req.ProposalBlockSlot,
)
}
}
func TestProposeBlock_PendingAttestationsFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.Eth1DataResponse{
Eth1Data: &pbp2p.Eth1Data{BlockHash32: []byte{'B', 'L', 'O', 'C', 'K'}},
}, nil /*err*/)
m.beaconClient.EXPECT().ForkData(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.Fork{
Epoch: params.BeaconConfig().GenesisEpoch,
CurrentVersion: 0,
PreviousVersion: 0,
}, nil /*err*/)
m.proposerClient.EXPECT().PendingAttestations(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}),
).Return(nil, errors.New("failed"))
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
testutil.AssertLogsContain(t, hook, "Failed to fetch pending attestations")
}
func TestProposeBlock_ComputeStateFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.Eth1DataResponse{}, nil /*err*/)
m.beaconClient.EXPECT().ForkData(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.Fork{
Epoch: params.BeaconConfig().GenesisEpoch,
CurrentVersion: 0,
PreviousVersion: 0,
}, nil /*err*/)
m.proposerClient.EXPECT().PendingAttestations(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}),
).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil)
m.proposerClient.EXPECT().ComputeStateRoot(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(nil /*response*/, errors.New("something bad happened"))
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
testutil.AssertLogsContain(t, hook, "something bad happened")
}
func TestProposeBlock_UsesComputedState(t *testing.T) {
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.Eth1DataResponse{}, nil /*err*/)
m.beaconClient.EXPECT().ForkData(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.Fork{
Epoch: params.BeaconConfig().GenesisEpoch,
CurrentVersion: 0,
PreviousVersion: 0,
}, nil /*err*/)
m.proposerClient.EXPECT().PendingAttestations(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}),
).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil)
var broadcastedBlock *pbp2p.BeaconBlock
m.proposerClient.EXPECT().ProposeBlock(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Do(func(_ context.Context, blk *pbp2p.BeaconBlock) {
broadcastedBlock = blk
}).Return(&pb.ProposeResponse{}, nil /*error*/)
computedStateRoot := []byte{'T', 'E', 'S', 'T'}
m.proposerClient.EXPECT().ComputeStateRoot(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(
&pb.StateRootResponse{
StateRoot: computedStateRoot,
},
nil, // err
)
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
if !bytes.Equal(broadcastedBlock.StateRootHash32, computedStateRoot) {
t.Errorf("Unexpected state root hash. want=%#x got=%#x", computedStateRoot, broadcastedBlock.StateRootHash32)
}
}
func TestProposeBlock_BroadcastsABlock(t *testing.T) {
validator, m, finish := setup(t)
defer finish()
m.beaconClient.EXPECT().CanonicalHead(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.BeaconBlock{}, nil /*err*/)
m.beaconClient.EXPECT().PendingDeposits(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.PendingDepositsResponse{}, nil /*err*/)
m.beaconClient.EXPECT().Eth1Data(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pb.Eth1DataResponse{}, nil /*err*/)
m.beaconClient.EXPECT().ForkData(
gomock.Any(), // ctx
gomock.Eq(&ptypes.Empty{}),
).Return(&pbp2p.Fork{
Epoch: params.BeaconConfig().GenesisEpoch,
CurrentVersion: 0,
PreviousVersion: 0,
}, nil /*err*/)
m.proposerClient.EXPECT().PendingAttestations(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}),
).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil)
m.proposerClient.EXPECT().ComputeStateRoot(
gomock.Any(), // context
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(&pb.StateRootResponse{
StateRoot: []byte{'F'},
}, nil /*err*/)
m.proposerClient.EXPECT().ProposeBlock(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}),
).Return(&pb.ProposeResponse{}, nil /*error*/)
validator.ProposeBlock(context.Background(), 55, hex.EncodeToString(validatorKey.PublicKey.Marshal()))
}