github.com/prysmaticlabs/prysm@v1.4.4/validator/client/propose_test.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"errors"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	lru "github.com/hashicorp/golang-lru"
    13  	types "github.com/prysmaticlabs/eth2-types"
    14  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    15  	validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    16  	"github.com/prysmaticlabs/prysm/shared/bls"
    17  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    18  	"github.com/prysmaticlabs/prysm/shared/mock"
    19  	"github.com/prysmaticlabs/prysm/shared/params"
    20  	"github.com/prysmaticlabs/prysm/shared/testutil"
    21  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    22  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    23  	testing2 "github.com/prysmaticlabs/prysm/validator/db/testing"
    24  	"github.com/prysmaticlabs/prysm/validator/graffiti"
    25  	logTest "github.com/sirupsen/logrus/hooks/test"
    26  	grpc "google.golang.org/grpc"
    27  	"google.golang.org/protobuf/types/known/timestamppb"
    28  )
    29  
    30  type mocks struct {
    31  	validatorClient *mock.MockBeaconNodeValidatorClient
    32  	nodeClient      *mock.MockNodeClient
    33  	signExitFunc    func(context.Context, *validatorpb.SignRequest) (bls.Signature, error)
    34  }
    35  
    36  type mockSignature struct{}
    37  
    38  func (mockSignature) Verify(bls.PublicKey, []byte) bool {
    39  	return true
    40  }
    41  func (mockSignature) AggregateVerify([]bls.PublicKey, [][32]byte) bool {
    42  	return true
    43  }
    44  func (mockSignature) FastAggregateVerify([]bls.PublicKey, [32]byte) bool {
    45  	return true
    46  }
    47  func (mockSignature) Eth2FastAggregateVerify([]bls.PublicKey, [32]byte) bool {
    48  	return true
    49  }
    50  func (mockSignature) Marshal() []byte {
    51  	return make([]byte, 32)
    52  }
    53  func (m mockSignature) Copy() bls.Signature {
    54  	return m
    55  }
    56  
    57  func setup(t *testing.T) (*validator, *mocks, bls.SecretKey, func()) {
    58  	validatorKey, err := bls.RandKey()
    59  	require.NoError(t, err)
    60  	pubKey := [48]byte{}
    61  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
    62  	valDB := testing2.SetupDB(t, [][48]byte{pubKey})
    63  	ctrl := gomock.NewController(t)
    64  	m := &mocks{
    65  		validatorClient: mock.NewMockBeaconNodeValidatorClient(ctrl),
    66  		nodeClient:      mock.NewMockNodeClient(ctrl),
    67  		signExitFunc: func(ctx context.Context, req *validatorpb.SignRequest) (bls.Signature, error) {
    68  			return mockSignature{}, nil
    69  		},
    70  	}
    71  
    72  	aggregatedSlotCommitteeIDCache, err := lru.New(int(params.BeaconConfig().MaxCommitteesPerSlot))
    73  	require.NoError(t, err)
    74  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
    75  	km := &mockKeymanager{
    76  		keysMap: map[[48]byte]bls.SecretKey{
    77  			pubKey: validatorKey,
    78  		},
    79  	}
    80  	validator := &validator{
    81  		db:                             valDB,
    82  		keyManager:                     km,
    83  		validatorClient:                m.validatorClient,
    84  		graffiti:                       []byte{},
    85  		attLogs:                        make(map[[32]byte]*attSubmitted),
    86  		aggregatedSlotCommitteeIDCache: aggregatedSlotCommitteeIDCache,
    87  	}
    88  
    89  	return validator, m, validatorKey, ctrl.Finish
    90  }
    91  
    92  func TestProposeBlock_DoesNotProposeGenesisBlock(t *testing.T) {
    93  	hook := logTest.NewGlobal()
    94  	validator, _, validatorKey, finish := setup(t)
    95  	defer finish()
    96  	pubKey := [48]byte{}
    97  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
    98  	validator.ProposeBlock(context.Background(), 0, pubKey)
    99  
   100  	require.LogsContain(t, hook, "Assigned to genesis slot, skipping proposal")
   101  }
   102  
   103  func TestProposeBlock_DomainDataFailed(t *testing.T) {
   104  	hook := logTest.NewGlobal()
   105  	validator, m, validatorKey, finish := setup(t)
   106  	defer finish()
   107  	pubKey := [48]byte{}
   108  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   109  
   110  	m.validatorClient.EXPECT().DomainData(
   111  		gomock.Any(), // ctx
   112  		gomock.Any(), // epoch
   113  	).Return(nil /*response*/, errors.New("uh oh"))
   114  
   115  	validator.ProposeBlock(context.Background(), 1, pubKey)
   116  	require.LogsContain(t, hook, "Failed to sign randao reveal")
   117  }
   118  
   119  func TestProposeBlock_DomainDataIsNil(t *testing.T) {
   120  	hook := logTest.NewGlobal()
   121  	validator, m, validatorKey, finish := setup(t)
   122  	defer finish()
   123  	pubKey := [48]byte{}
   124  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   125  
   126  	m.validatorClient.EXPECT().DomainData(
   127  		gomock.Any(), // ctx
   128  		gomock.Any(), // epoch
   129  	).Return(nil /*response*/, nil)
   130  
   131  	validator.ProposeBlock(context.Background(), 1, pubKey)
   132  	require.LogsContain(t, hook, domainDataErr)
   133  }
   134  
   135  func TestProposeBlock_RequestBlockFailed(t *testing.T) {
   136  	hook := logTest.NewGlobal()
   137  	validator, m, validatorKey, finish := setup(t)
   138  	defer finish()
   139  	pubKey := [48]byte{}
   140  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   141  
   142  	m.validatorClient.EXPECT().DomainData(
   143  		gomock.Any(), // ctx
   144  		gomock.Any(), // epoch
   145  	).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   146  
   147  	m.validatorClient.EXPECT().GetBlock(
   148  		gomock.Any(), // ctx
   149  		gomock.Any(), // block request
   150  	).Return(nil /*response*/, errors.New("uh oh"))
   151  
   152  	validator.ProposeBlock(context.Background(), 1, pubKey)
   153  	require.LogsContain(t, hook, "Failed to request block from beacon node")
   154  }
   155  
   156  func TestProposeBlock_ProposeBlockFailed(t *testing.T) {
   157  	hook := logTest.NewGlobal()
   158  	validator, m, validatorKey, finish := setup(t)
   159  	defer finish()
   160  	pubKey := [48]byte{}
   161  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   162  
   163  	m.validatorClient.EXPECT().DomainData(
   164  		gomock.Any(), // ctx
   165  		gomock.Any(), // epoch
   166  	).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   167  
   168  	m.validatorClient.EXPECT().GetBlock(
   169  		gomock.Any(), // ctx
   170  		gomock.Any(),
   171  	).Return(testutil.NewBeaconBlock().Block, nil /*err*/)
   172  
   173  	m.validatorClient.EXPECT().DomainData(
   174  		gomock.Any(), // ctx
   175  		gomock.Any(), // epoch
   176  	).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   177  
   178  	m.validatorClient.EXPECT().ProposeBlock(
   179  		gomock.Any(), // ctx
   180  		gomock.AssignableToTypeOf(&ethpb.SignedBeaconBlock{}),
   181  	).Return(nil /*response*/, errors.New("uh oh"))
   182  
   183  	validator.ProposeBlock(context.Background(), 1, pubKey)
   184  	require.LogsContain(t, hook, "Failed to propose block")
   185  }
   186  
   187  func TestProposeBlock_BlocksDoubleProposal(t *testing.T) {
   188  	hook := logTest.NewGlobal()
   189  	validator, m, validatorKey, finish := setup(t)
   190  	defer finish()
   191  	pubKey := [48]byte{}
   192  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   193  
   194  	dummyRoot := [32]byte{}
   195  	// Save a dummy proposal history at slot 0.
   196  	err := validator.db.SaveProposalHistoryForSlot(context.Background(), pubKey, 0, dummyRoot[:])
   197  	require.NoError(t, err)
   198  
   199  	m.validatorClient.EXPECT().DomainData(
   200  		gomock.Any(), // ctx
   201  		gomock.Any(), // epoch
   202  	).Times(1).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   203  
   204  	testBlock := testutil.NewBeaconBlock()
   205  	slot := params.BeaconConfig().SlotsPerEpoch*5 + 2
   206  	testBlock.Block.Slot = slot
   207  	m.validatorClient.EXPECT().GetBlock(
   208  		gomock.Any(), // ctx
   209  		gomock.Any(),
   210  	).Return(testBlock.Block, nil /*err*/)
   211  
   212  	secondTestBlock := testutil.NewBeaconBlock()
   213  	secondTestBlock.Block.Slot = slot
   214  	graffiti := [32]byte{}
   215  	copy(graffiti[:], "someothergraffiti")
   216  	secondTestBlock.Block.Body.Graffiti = graffiti[:]
   217  	m.validatorClient.EXPECT().GetBlock(
   218  		gomock.Any(), // ctx
   219  		gomock.Any(),
   220  	).Return(secondTestBlock.Block, nil /*err*/)
   221  
   222  	m.validatorClient.EXPECT().DomainData(
   223  		gomock.Any(), // ctx
   224  		gomock.Any(), // epoch
   225  	).Times(3).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   226  
   227  	m.validatorClient.EXPECT().ProposeBlock(
   228  		gomock.Any(), // ctx
   229  		gomock.AssignableToTypeOf(&ethpb.SignedBeaconBlock{}),
   230  	).Return(&ethpb.ProposeResponse{BlockRoot: make([]byte, 32)}, nil /*error*/)
   231  
   232  	validator.ProposeBlock(context.Background(), slot, pubKey)
   233  	require.LogsDoNotContain(t, hook, failedPreBlockSignLocalErr)
   234  
   235  	validator.ProposeBlock(context.Background(), slot, pubKey)
   236  	require.LogsContain(t, hook, failedPreBlockSignLocalErr)
   237  }
   238  
   239  func TestProposeBlock_BlocksDoubleProposal_After54KEpochs(t *testing.T) {
   240  	hook := logTest.NewGlobal()
   241  	validator, m, validatorKey, finish := setup(t)
   242  	defer finish()
   243  	pubKey := [48]byte{}
   244  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   245  
   246  	dummyRoot := [32]byte{}
   247  	// Save a dummy proposal history at slot 0.
   248  	err := validator.db.SaveProposalHistoryForSlot(context.Background(), pubKey, 0, dummyRoot[:])
   249  	require.NoError(t, err)
   250  
   251  	m.validatorClient.EXPECT().DomainData(
   252  		gomock.Any(), // ctx
   253  		gomock.Any(), // epoch
   254  	).Times(1).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   255  
   256  	testBlock := testutil.NewBeaconBlock()
   257  	farFuture := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().WeakSubjectivityPeriod + 9))
   258  	testBlock.Block.Slot = farFuture
   259  	m.validatorClient.EXPECT().GetBlock(
   260  		gomock.Any(), // ctx
   261  		gomock.Any(),
   262  	).Return(testBlock.Block, nil /*err*/)
   263  
   264  	secondTestBlock := testutil.NewBeaconBlock()
   265  	secondTestBlock.Block.Slot = farFuture
   266  	graffiti := [32]byte{}
   267  	copy(graffiti[:], "someothergraffiti")
   268  	secondTestBlock.Block.Body.Graffiti = graffiti[:]
   269  	m.validatorClient.EXPECT().GetBlock(
   270  		gomock.Any(), // ctx
   271  		gomock.Any(),
   272  	).Return(secondTestBlock.Block, nil /*err*/)
   273  
   274  	m.validatorClient.EXPECT().DomainData(
   275  		gomock.Any(), // ctx
   276  		gomock.Any(), // epoch
   277  	).Times(3).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   278  
   279  	m.validatorClient.EXPECT().ProposeBlock(
   280  		gomock.Any(), // ctx
   281  		gomock.AssignableToTypeOf(&ethpb.SignedBeaconBlock{}),
   282  	).Return(&ethpb.ProposeResponse{BlockRoot: make([]byte, 32)}, nil /*error*/)
   283  
   284  	validator.ProposeBlock(context.Background(), farFuture, pubKey)
   285  	require.LogsDoNotContain(t, hook, failedPreBlockSignLocalErr)
   286  
   287  	validator.ProposeBlock(context.Background(), farFuture, pubKey)
   288  	require.LogsContain(t, hook, failedPreBlockSignLocalErr)
   289  }
   290  
   291  func TestProposeBlock_AllowsPastProposals(t *testing.T) {
   292  	hook := logTest.NewGlobal()
   293  	validator, m, validatorKey, finish := setup(t)
   294  	defer finish()
   295  	pubKey := [48]byte{}
   296  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   297  
   298  	// Save a dummy proposal history at slot 0.
   299  	err := validator.db.SaveProposalHistoryForSlot(context.Background(), pubKey, 0, []byte{})
   300  	require.NoError(t, err)
   301  
   302  	m.validatorClient.EXPECT().DomainData(
   303  		gomock.Any(), // ctx
   304  		gomock.Any(), // epoch
   305  	).Times(2).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   306  
   307  	farAhead := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().WeakSubjectivityPeriod + 9))
   308  	blk := testutil.NewBeaconBlock()
   309  	blk.Block.Slot = farAhead
   310  	m.validatorClient.EXPECT().GetBlock(
   311  		gomock.Any(), // ctx
   312  		gomock.Any(),
   313  	).Return(blk.Block, nil /*err*/)
   314  
   315  	m.validatorClient.EXPECT().DomainData(
   316  		gomock.Any(), // ctx
   317  		gomock.Any(), // epoch
   318  	).Times(2).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   319  
   320  	m.validatorClient.EXPECT().ProposeBlock(
   321  		gomock.Any(), // ctx
   322  		gomock.AssignableToTypeOf(&ethpb.SignedBeaconBlock{}),
   323  	).Times(2).Return(&ethpb.ProposeResponse{BlockRoot: make([]byte, 32)}, nil /*error*/)
   324  
   325  	validator.ProposeBlock(context.Background(), farAhead, pubKey)
   326  	require.LogsDoNotContain(t, hook, failedPreBlockSignLocalErr)
   327  
   328  	past := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().WeakSubjectivityPeriod - 400))
   329  	blk2 := testutil.NewBeaconBlock()
   330  	blk2.Block.Slot = past
   331  	m.validatorClient.EXPECT().GetBlock(
   332  		gomock.Any(), // ctx
   333  		gomock.Any(),
   334  	).Return(blk2.Block, nil /*err*/)
   335  	validator.ProposeBlock(context.Background(), past, pubKey)
   336  	require.LogsDoNotContain(t, hook, failedPreBlockSignLocalErr)
   337  }
   338  
   339  func TestProposeBlock_AllowsSameEpoch(t *testing.T) {
   340  	hook := logTest.NewGlobal()
   341  	validator, m, validatorKey, finish := setup(t)
   342  	defer finish()
   343  	pubKey := [48]byte{}
   344  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   345  
   346  	// Save a dummy proposal history at slot 0.
   347  	err := validator.db.SaveProposalHistoryForSlot(context.Background(), pubKey, 0, []byte{})
   348  	require.NoError(t, err)
   349  
   350  	m.validatorClient.EXPECT().DomainData(
   351  		gomock.Any(), // ctx
   352  		gomock.Any(), // epoch
   353  	).Times(2).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   354  
   355  	farAhead := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().WeakSubjectivityPeriod + 9))
   356  	blk := testutil.NewBeaconBlock()
   357  	blk.Block.Slot = farAhead
   358  	m.validatorClient.EXPECT().GetBlock(
   359  		gomock.Any(), // ctx
   360  		gomock.Any(),
   361  	).Return(blk.Block, nil /*err*/)
   362  
   363  	m.validatorClient.EXPECT().DomainData(
   364  		gomock.Any(), // ctx
   365  		gomock.Any(), // epoch
   366  	).Times(2).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   367  
   368  	m.validatorClient.EXPECT().ProposeBlock(
   369  		gomock.Any(), // ctx
   370  		gomock.AssignableToTypeOf(&ethpb.SignedBeaconBlock{}),
   371  	).Times(2).Return(&ethpb.ProposeResponse{BlockRoot: make([]byte, 32)}, nil /*error*/)
   372  
   373  	validator.ProposeBlock(context.Background(), farAhead, pubKey)
   374  	require.LogsDoNotContain(t, hook, failedPreBlockSignLocalErr)
   375  
   376  	blk2 := testutil.NewBeaconBlock()
   377  	blk2.Block.Slot = farAhead - 4
   378  	m.validatorClient.EXPECT().GetBlock(
   379  		gomock.Any(), // ctx
   380  		gomock.Any(),
   381  	).Return(blk2.Block, nil /*err*/)
   382  
   383  	validator.ProposeBlock(context.Background(), farAhead-4, pubKey)
   384  	require.LogsDoNotContain(t, hook, failedPreBlockSignLocalErr)
   385  }
   386  
   387  func TestProposeBlock_BroadcastsBlock(t *testing.T) {
   388  	validator, m, validatorKey, finish := setup(t)
   389  	defer finish()
   390  	pubKey := [48]byte{}
   391  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   392  
   393  	m.validatorClient.EXPECT().DomainData(
   394  		gomock.Any(), // ctx
   395  		gomock.Any(), // epoch
   396  	).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   397  
   398  	m.validatorClient.EXPECT().GetBlock(
   399  		gomock.Any(), // ctx
   400  		gomock.Any(),
   401  	).Return(testutil.NewBeaconBlock().Block, nil /*err*/)
   402  
   403  	m.validatorClient.EXPECT().DomainData(
   404  		gomock.Any(), // ctx
   405  		gomock.Any(), // epoch
   406  	).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   407  
   408  	m.validatorClient.EXPECT().ProposeBlock(
   409  		gomock.Any(), // ctx
   410  		gomock.AssignableToTypeOf(&ethpb.SignedBeaconBlock{}),
   411  	).Return(&ethpb.ProposeResponse{BlockRoot: make([]byte, 32)}, nil /*error*/)
   412  
   413  	validator.ProposeBlock(context.Background(), 1, pubKey)
   414  }
   415  
   416  func TestProposeBlock_BroadcastsBlock_WithGraffiti(t *testing.T) {
   417  	validator, m, validatorKey, finish := setup(t)
   418  	defer finish()
   419  	pubKey := [48]byte{}
   420  	copy(pubKey[:], validatorKey.PublicKey().Marshal())
   421  
   422  	validator.graffiti = []byte("12345678901234567890123456789012")
   423  
   424  	m.validatorClient.EXPECT().DomainData(
   425  		gomock.Any(), // ctx
   426  		gomock.Any(), // epoch
   427  	).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   428  
   429  	blk := testutil.NewBeaconBlock()
   430  	blk.Block.Body.Graffiti = validator.graffiti
   431  	m.validatorClient.EXPECT().GetBlock(
   432  		gomock.Any(), // ctx
   433  		gomock.Any(),
   434  	).Return(blk.Block, nil /*err*/)
   435  
   436  	m.validatorClient.EXPECT().DomainData(
   437  		gomock.Any(), // ctx
   438  		gomock.Any(), // epoch
   439  	).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
   440  
   441  	var sentBlock *ethpb.SignedBeaconBlock
   442  
   443  	m.validatorClient.EXPECT().ProposeBlock(
   444  		gomock.Any(), // ctx
   445  		gomock.AssignableToTypeOf(&ethpb.SignedBeaconBlock{}),
   446  	).DoAndReturn(func(ctx context.Context, block *ethpb.SignedBeaconBlock, arg2 ...grpc.CallOption) (*ethpb.ProposeResponse, error) {
   447  		sentBlock = block
   448  		return &ethpb.ProposeResponse{BlockRoot: make([]byte, 32)}, nil
   449  	})
   450  
   451  	validator.ProposeBlock(context.Background(), 1, pubKey)
   452  	assert.Equal(t, string(validator.graffiti), string(sentBlock.Block.Body.Graffiti))
   453  }
   454  
   455  func TestProposeExit_ValidatorIndexFailed(t *testing.T) {
   456  	_, m, validatorKey, finish := setup(t)
   457  	defer finish()
   458  
   459  	m.validatorClient.EXPECT().ValidatorIndex(
   460  		gomock.Any(),
   461  		gomock.Any(),
   462  	).Return(nil, errors.New("uh oh"))
   463  
   464  	err := ProposeExit(
   465  		context.Background(),
   466  		m.validatorClient,
   467  		m.nodeClient,
   468  		m.signExitFunc,
   469  		validatorKey.PublicKey().Marshal(),
   470  	)
   471  	assert.NotNil(t, err)
   472  	assert.ErrorContains(t, "uh oh", err)
   473  	assert.ErrorContains(t, "gRPC call to get validator index failed", err)
   474  }
   475  
   476  func TestProposeExit_GetGenesisFailed(t *testing.T) {
   477  	_, m, validatorKey, finish := setup(t)
   478  	defer finish()
   479  
   480  	m.validatorClient.EXPECT().
   481  		ValidatorIndex(gomock.Any(), gomock.Any()).
   482  		Return(nil, nil)
   483  
   484  	m.nodeClient.EXPECT().
   485  		GetGenesis(gomock.Any(), gomock.Any()).
   486  		Return(nil, errors.New("uh oh"))
   487  
   488  	err := ProposeExit(
   489  		context.Background(),
   490  		m.validatorClient,
   491  		m.nodeClient,
   492  		m.signExitFunc,
   493  		validatorKey.PublicKey().Marshal(),
   494  	)
   495  	assert.NotNil(t, err)
   496  	assert.ErrorContains(t, "uh oh", err)
   497  	assert.ErrorContains(t, "gRPC call to get genesis time failed", err)
   498  }
   499  
   500  func TestProposeExit_DomainDataFailed(t *testing.T) {
   501  	_, m, validatorKey, finish := setup(t)
   502  	defer finish()
   503  
   504  	m.validatorClient.EXPECT().
   505  		ValidatorIndex(gomock.Any(), gomock.Any()).
   506  		Return(&ethpb.ValidatorIndexResponse{Index: 1}, nil)
   507  
   508  	// Any time in the past will suffice
   509  	genesisTime := &timestamppb.Timestamp{
   510  		Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
   511  	}
   512  
   513  	m.nodeClient.EXPECT().
   514  		GetGenesis(gomock.Any(), gomock.Any()).
   515  		Return(&ethpb.Genesis{GenesisTime: genesisTime}, nil)
   516  
   517  	m.validatorClient.EXPECT().
   518  		DomainData(gomock.Any(), gomock.Any()).
   519  		Return(nil, errors.New("uh oh"))
   520  
   521  	err := ProposeExit(
   522  		context.Background(),
   523  		m.validatorClient,
   524  		m.nodeClient,
   525  		m.signExitFunc,
   526  		validatorKey.PublicKey().Marshal(),
   527  	)
   528  	assert.NotNil(t, err)
   529  	assert.ErrorContains(t, domainDataErr, err)
   530  	assert.ErrorContains(t, "uh oh", err)
   531  	assert.ErrorContains(t, "failed to sign voluntary exit", err)
   532  }
   533  
   534  func TestProposeExit_DomainDataIsNil(t *testing.T) {
   535  	_, m, validatorKey, finish := setup(t)
   536  	defer finish()
   537  
   538  	m.validatorClient.EXPECT().
   539  		ValidatorIndex(gomock.Any(), gomock.Any()).
   540  		Return(&ethpb.ValidatorIndexResponse{Index: 1}, nil)
   541  
   542  	// Any time in the past will suffice
   543  	genesisTime := &timestamppb.Timestamp{
   544  		Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
   545  	}
   546  
   547  	m.nodeClient.EXPECT().
   548  		GetGenesis(gomock.Any(), gomock.Any()).
   549  		Return(&ethpb.Genesis{GenesisTime: genesisTime}, nil)
   550  
   551  	m.validatorClient.EXPECT().
   552  		DomainData(gomock.Any(), gomock.Any()).
   553  		Return(nil, nil)
   554  
   555  	err := ProposeExit(
   556  		context.Background(),
   557  		m.validatorClient,
   558  		m.nodeClient,
   559  		m.signExitFunc,
   560  		validatorKey.PublicKey().Marshal(),
   561  	)
   562  	assert.NotNil(t, err)
   563  	assert.ErrorContains(t, domainDataErr, err)
   564  	assert.ErrorContains(t, "failed to sign voluntary exit", err)
   565  }
   566  
   567  func TestProposeBlock_ProposeExitFailed(t *testing.T) {
   568  	_, m, validatorKey, finish := setup(t)
   569  	defer finish()
   570  
   571  	m.validatorClient.EXPECT().
   572  		ValidatorIndex(gomock.Any(), gomock.Any()).
   573  		Return(&ethpb.ValidatorIndexResponse{Index: 1}, nil)
   574  
   575  	// Any time in the past will suffice
   576  	genesisTime := &timestamppb.Timestamp{
   577  		Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
   578  	}
   579  
   580  	m.nodeClient.EXPECT().
   581  		GetGenesis(gomock.Any(), gomock.Any()).
   582  		Return(&ethpb.Genesis{GenesisTime: genesisTime}, nil)
   583  
   584  	m.validatorClient.EXPECT().
   585  		DomainData(gomock.Any(), gomock.Any()).
   586  		Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil)
   587  
   588  	m.validatorClient.EXPECT().
   589  		ProposeExit(gomock.Any(), gomock.AssignableToTypeOf(&ethpb.SignedVoluntaryExit{})).
   590  		Return(nil, errors.New("uh oh"))
   591  
   592  	err := ProposeExit(
   593  		context.Background(),
   594  		m.validatorClient,
   595  		m.nodeClient,
   596  		m.signExitFunc,
   597  		validatorKey.PublicKey().Marshal(),
   598  	)
   599  	assert.NotNil(t, err)
   600  	assert.ErrorContains(t, "uh oh", err)
   601  	assert.ErrorContains(t, "failed to propose voluntary exit", err)
   602  }
   603  
   604  func TestProposeExit_BroadcastsBlock(t *testing.T) {
   605  	_, m, validatorKey, finish := setup(t)
   606  	defer finish()
   607  
   608  	m.validatorClient.EXPECT().
   609  		ValidatorIndex(gomock.Any(), gomock.Any()).
   610  		Return(&ethpb.ValidatorIndexResponse{Index: 1}, nil)
   611  
   612  	// Any time in the past will suffice
   613  	genesisTime := &timestamppb.Timestamp{
   614  		Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
   615  	}
   616  
   617  	m.nodeClient.EXPECT().
   618  		GetGenesis(gomock.Any(), gomock.Any()).
   619  		Return(&ethpb.Genesis{GenesisTime: genesisTime}, nil)
   620  
   621  	m.validatorClient.EXPECT().
   622  		DomainData(gomock.Any(), gomock.Any()).
   623  		Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil)
   624  
   625  	m.validatorClient.EXPECT().
   626  		ProposeExit(gomock.Any(), gomock.AssignableToTypeOf(&ethpb.SignedVoluntaryExit{})).
   627  		Return(&ethpb.ProposeExitResponse{}, nil)
   628  
   629  	assert.NoError(t, ProposeExit(
   630  		context.Background(),
   631  		m.validatorClient,
   632  		m.nodeClient,
   633  		m.signExitFunc,
   634  		validatorKey.PublicKey().Marshal(),
   635  	))
   636  }
   637  
   638  func TestSignBlock(t *testing.T) {
   639  	validator, m, _, finish := setup(t)
   640  	defer finish()
   641  
   642  	secretKey, err := bls.SecretKeyFromBytes(bytesutil.PadTo([]byte{1}, 32))
   643  	require.NoError(t, err, "Failed to generate key from bytes")
   644  	publicKey := secretKey.PublicKey()
   645  	proposerDomain := make([]byte, 32)
   646  	m.validatorClient.EXPECT().
   647  		DomainData(gomock.Any(), gomock.Any()).
   648  		Return(&ethpb.DomainResponse{SignatureDomain: proposerDomain}, nil)
   649  	ctx := context.Background()
   650  	blk := testutil.NewBeaconBlock()
   651  	blk.Block.Slot = 1
   652  	blk.Block.ProposerIndex = 100
   653  	var pubKey [48]byte
   654  	copy(pubKey[:], publicKey.Marshal())
   655  	km := &mockKeymanager{
   656  		keysMap: map[[48]byte]bls.SecretKey{
   657  			pubKey: secretKey,
   658  		},
   659  	}
   660  	validator.keyManager = km
   661  	sig, domain, err := validator.signBlock(ctx, pubKey, 0, blk.Block)
   662  	require.NoError(t, err, "%x,%x,%v", sig, domain.SignatureDomain, err)
   663  	require.Equal(t, "a049e1dc723e5a8b5bd14f292973572dffd53785ddb337"+
   664  		"82f20bf762cbe10ee7b9b4f5ae1ad6ff2089d352403750bed402b94b58469c072536"+
   665  		"faa9a09a88beaff697404ca028b1c7052b0de37dbcff985dfa500459783370312bdd"+
   666  		"36d6e0f224", hex.EncodeToString(sig))
   667  	// proposer domain
   668  	require.DeepEqual(t, proposerDomain, domain.SignatureDomain)
   669  }
   670  
   671  func TestGetGraffiti_Ok(t *testing.T) {
   672  	ctrl := gomock.NewController(t)
   673  	m := &mocks{
   674  		validatorClient: mock.NewMockBeaconNodeValidatorClient(ctrl),
   675  	}
   676  	pubKey := [48]byte{'a'}
   677  	tests := []struct {
   678  		name string
   679  		v    *validator
   680  		want []byte
   681  	}{
   682  		{name: "use default cli graffiti",
   683  			v: &validator{
   684  				graffiti: []byte{'b'},
   685  				graffitiStruct: &graffiti.Graffiti{
   686  					Default: "c",
   687  					Random:  []string{"d", "e"},
   688  					Specific: map[types.ValidatorIndex]string{
   689  						1: "f",
   690  						2: "g",
   691  					},
   692  				},
   693  			},
   694  			want: []byte{'b'},
   695  		},
   696  		{name: "use default file graffiti",
   697  			v: &validator{
   698  				validatorClient: m.validatorClient,
   699  				graffitiStruct: &graffiti.Graffiti{
   700  					Default: "c",
   701  				},
   702  			},
   703  			want: []byte{'c'},
   704  		},
   705  		{name: "use random file graffiti",
   706  			v: &validator{
   707  				validatorClient: m.validatorClient,
   708  				graffitiStruct: &graffiti.Graffiti{
   709  					Random:  []string{"d"},
   710  					Default: "c",
   711  				},
   712  			},
   713  			want: []byte{'d'},
   714  		},
   715  		{name: "use validator file graffiti, has validator",
   716  			v: &validator{
   717  				validatorClient: m.validatorClient,
   718  				graffitiStruct: &graffiti.Graffiti{
   719  					Random:  []string{"d"},
   720  					Default: "c",
   721  					Specific: map[types.ValidatorIndex]string{
   722  						1: "f",
   723  						2: "g",
   724  					},
   725  				},
   726  			},
   727  			want: []byte{'g'},
   728  		},
   729  		{name: "use validator file graffiti, none specified",
   730  			v: &validator{
   731  				validatorClient: m.validatorClient,
   732  				graffitiStruct:  &graffiti.Graffiti{},
   733  			},
   734  			want: []byte{},
   735  		},
   736  	}
   737  
   738  	for _, tt := range tests {
   739  		t.Run(tt.name, func(t *testing.T) {
   740  			if !strings.Contains(tt.name, "use default cli graffiti") {
   741  				m.validatorClient.EXPECT().
   742  					ValidatorIndex(gomock.Any(), &ethpb.ValidatorIndexRequest{PublicKey: pubKey[:]}).
   743  					Return(&ethpb.ValidatorIndexResponse{Index: 2}, nil)
   744  			}
   745  			got, err := tt.v.getGraffiti(context.Background(), pubKey)
   746  			require.NoError(t, err)
   747  			require.DeepEqual(t, tt.want, got)
   748  		})
   749  	}
   750  }
   751  
   752  func TestGetGraffitiOrdered_Ok(t *testing.T) {
   753  	pubKey := [48]byte{'a'}
   754  	valDB := testing2.SetupDB(t, [][48]byte{pubKey})
   755  	ctrl := gomock.NewController(t)
   756  	m := &mocks{
   757  		validatorClient: mock.NewMockBeaconNodeValidatorClient(ctrl),
   758  	}
   759  	m.validatorClient.EXPECT().
   760  		ValidatorIndex(gomock.Any(), &ethpb.ValidatorIndexRequest{PublicKey: pubKey[:]}).
   761  		Times(5).
   762  		Return(&ethpb.ValidatorIndexResponse{Index: 2}, nil)
   763  
   764  	v := &validator{
   765  		db:              valDB,
   766  		validatorClient: m.validatorClient,
   767  		graffitiStruct: &graffiti.Graffiti{
   768  			Ordered: []string{"a", "b", "c"},
   769  			Default: "d",
   770  		},
   771  	}
   772  	for _, want := range [][]byte{{'a'}, {'b'}, {'c'}, {'d'}, {'d'}} {
   773  		got, err := v.getGraffiti(context.Background(), pubKey)
   774  		require.NoError(t, err)
   775  		require.DeepEqual(t, want, got)
   776  	}
   777  }