github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/prysm/v1alpha1/validator/assignments_test.go (about)

     1  package validator
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/golang/mock/gomock"
    10  	types "github.com/prysmaticlabs/eth2-types"
    11  	mockChain "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
    12  	"github.com/prysmaticlabs/prysm/beacon-chain/cache"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
    14  	statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
    15  	"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
    16  	dbutil "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
    17  	mockSync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing"
    18  	ethpbv1 "github.com/prysmaticlabs/prysm/proto/eth/v1"
    19  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    20  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    21  	"github.com/prysmaticlabs/prysm/shared/mock"
    22  	"github.com/prysmaticlabs/prysm/shared/params"
    23  	"github.com/prysmaticlabs/prysm/shared/testutil"
    24  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    25  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    26  )
    27  
    28  // pubKey is a helper to generate a well-formed public key.
    29  func pubKey(i uint64) []byte {
    30  	pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
    31  	binary.LittleEndian.PutUint64(pubKey, i)
    32  	return pubKey
    33  }
    34  
    35  func TestGetDuties_OK(t *testing.T) {
    36  	db := dbutil.SetupDB(t)
    37  
    38  	genesis := testutil.NewBeaconBlock()
    39  	depChainStart := params.BeaconConfig().MinGenesisActiveValidatorCount
    40  	deposits, _, err := testutil.DeterministicDepositsAndKeys(depChainStart)
    41  	require.NoError(t, err)
    42  	eth1Data, err := testutil.DeterministicEth1Data(len(deposits))
    43  	require.NoError(t, err)
    44  	bs, err := state.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
    45  	require.NoError(t, err, "Could not setup genesis bs")
    46  	genesisRoot, err := genesis.Block.HashTreeRoot()
    47  	require.NoError(t, err, "Could not get signing root")
    48  
    49  	pubKeys := make([][]byte, len(deposits))
    50  	indices := make([]uint64, len(deposits))
    51  	for i := 0; i < len(deposits); i++ {
    52  		pubKeys[i] = deposits[i].Data.PublicKey
    53  		indices[i] = uint64(i)
    54  	}
    55  
    56  	pubkeysAs48ByteType := make([][48]byte, len(pubKeys))
    57  	for i, pk := range pubKeys {
    58  		pubkeysAs48ByteType[i] = bytesutil.ToBytes48(pk)
    59  	}
    60  
    61  	chain := &mockChain.ChainService{
    62  		State: bs, Root: genesisRoot[:], Genesis: time.Now(),
    63  	}
    64  	vs := &Server{
    65  		BeaconDB:    db,
    66  		HeadFetcher: chain,
    67  		TimeFetcher: chain,
    68  		SyncChecker: &mockSync.Sync{IsSyncing: false},
    69  	}
    70  
    71  	// Test the first validator in registry.
    72  	req := &ethpb.DutiesRequest{
    73  		PublicKeys: [][]byte{deposits[0].Data.PublicKey},
    74  	}
    75  	res, err := vs.GetDuties(context.Background(), req)
    76  	require.NoError(t, err, "Could not call epoch committee assignment")
    77  	if res.CurrentEpochDuties[0].AttesterSlot > bs.Slot()+params.BeaconConfig().SlotsPerEpoch {
    78  		t.Errorf("Assigned slot %d can't be higher than %d",
    79  			res.CurrentEpochDuties[0].AttesterSlot, bs.Slot()+params.BeaconConfig().SlotsPerEpoch)
    80  	}
    81  
    82  	// Test the last validator in registry.
    83  	lastValidatorIndex := depChainStart - 1
    84  	req = &ethpb.DutiesRequest{
    85  		PublicKeys: [][]byte{deposits[lastValidatorIndex].Data.PublicKey},
    86  	}
    87  	res, err = vs.GetDuties(context.Background(), req)
    88  	require.NoError(t, err, "Could not call epoch committee assignment")
    89  	if res.CurrentEpochDuties[0].AttesterSlot > bs.Slot()+params.BeaconConfig().SlotsPerEpoch {
    90  		t.Errorf("Assigned slot %d can't be higher than %d",
    91  			res.CurrentEpochDuties[0].AttesterSlot, bs.Slot()+params.BeaconConfig().SlotsPerEpoch)
    92  	}
    93  
    94  	// We request for duties for all validators.
    95  	req = &ethpb.DutiesRequest{
    96  		PublicKeys: pubKeys,
    97  		Epoch:      0,
    98  	}
    99  	res, err = vs.GetDuties(context.Background(), req)
   100  	require.NoError(t, err, "Could not call epoch committee assignment")
   101  	for i := 0; i < len(res.CurrentEpochDuties); i++ {
   102  		assert.Equal(t, types.ValidatorIndex(i), res.CurrentEpochDuties[i].ValidatorIndex)
   103  	}
   104  }
   105  
   106  func TestGetDuties_SlotOutOfUpperBound(t *testing.T) {
   107  	chain := &mockChain.ChainService{
   108  		Genesis: time.Now(),
   109  	}
   110  	vs := &Server{
   111  		TimeFetcher: chain,
   112  	}
   113  	req := &ethpb.DutiesRequest{
   114  		Epoch: types.Epoch(chain.CurrentSlot()/params.BeaconConfig().SlotsPerEpoch + 2),
   115  	}
   116  	_, err := vs.duties(context.Background(), req)
   117  	require.ErrorContains(t, "can not be greater than next epoch", err)
   118  }
   119  
   120  func TestGetDuties_CurrentEpoch_ShouldNotFail(t *testing.T) {
   121  	db := dbutil.SetupDB(t)
   122  
   123  	genesis := testutil.NewBeaconBlock()
   124  	depChainStart := params.BeaconConfig().MinGenesisActiveValidatorCount
   125  	deposits, _, err := testutil.DeterministicDepositsAndKeys(depChainStart)
   126  	require.NoError(t, err)
   127  	eth1Data, err := testutil.DeterministicEth1Data(len(deposits))
   128  	require.NoError(t, err)
   129  	bState, err := state.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
   130  	require.NoError(t, err, "Could not setup genesis state")
   131  	// Set state to non-epoch start slot.
   132  	require.NoError(t, bState.SetSlot(5))
   133  
   134  	genesisRoot, err := genesis.Block.HashTreeRoot()
   135  	require.NoError(t, err, "Could not get signing root")
   136  
   137  	pubKeys := make([][48]byte, len(deposits))
   138  	indices := make([]uint64, len(deposits))
   139  	for i := 0; i < len(deposits); i++ {
   140  		pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
   141  		indices[i] = uint64(i)
   142  	}
   143  
   144  	chain := &mockChain.ChainService{
   145  		State: bState, Root: genesisRoot[:], Genesis: time.Now(),
   146  	}
   147  	vs := &Server{
   148  		BeaconDB:    db,
   149  		HeadFetcher: chain,
   150  		TimeFetcher: chain,
   151  		SyncChecker: &mockSync.Sync{IsSyncing: false},
   152  	}
   153  
   154  	// Test the first validator in registry.
   155  	req := &ethpb.DutiesRequest{
   156  		PublicKeys: [][]byte{deposits[0].Data.PublicKey},
   157  	}
   158  	res, err := vs.GetDuties(context.Background(), req)
   159  	require.NoError(t, err)
   160  	assert.Equal(t, 1, len(res.CurrentEpochDuties), "Expected 1 assignment")
   161  }
   162  
   163  func TestGetDuties_MultipleKeys_OK(t *testing.T) {
   164  	db := dbutil.SetupDB(t)
   165  
   166  	genesis := testutil.NewBeaconBlock()
   167  	depChainStart := uint64(64)
   168  
   169  	deposits, _, err := testutil.DeterministicDepositsAndKeys(depChainStart)
   170  	require.NoError(t, err)
   171  	eth1Data, err := testutil.DeterministicEth1Data(len(deposits))
   172  	require.NoError(t, err)
   173  	bs, err := state.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
   174  	require.NoError(t, err, "Could not setup genesis bs")
   175  	genesisRoot, err := genesis.Block.HashTreeRoot()
   176  	require.NoError(t, err, "Could not get signing root")
   177  
   178  	pubKeys := make([][48]byte, len(deposits))
   179  	indices := make([]uint64, len(deposits))
   180  	for i := 0; i < len(deposits); i++ {
   181  		pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
   182  		indices[i] = uint64(i)
   183  	}
   184  
   185  	chain := &mockChain.ChainService{
   186  		State: bs, Root: genesisRoot[:], Genesis: time.Now(),
   187  	}
   188  	vs := &Server{
   189  		BeaconDB:    db,
   190  		HeadFetcher: chain,
   191  		TimeFetcher: chain,
   192  		SyncChecker: &mockSync.Sync{IsSyncing: false},
   193  	}
   194  
   195  	pubkey0 := deposits[0].Data.PublicKey
   196  	pubkey1 := deposits[1].Data.PublicKey
   197  
   198  	// Test the first validator in registry.
   199  	req := &ethpb.DutiesRequest{
   200  		PublicKeys: [][]byte{pubkey0, pubkey1},
   201  	}
   202  	res, err := vs.GetDuties(context.Background(), req)
   203  	require.NoError(t, err, "Could not call epoch committee assignment")
   204  	assert.Equal(t, 2, len(res.CurrentEpochDuties))
   205  	assert.Equal(t, types.Slot(4), res.CurrentEpochDuties[0].AttesterSlot)
   206  	assert.Equal(t, types.Slot(4), res.CurrentEpochDuties[1].AttesterSlot)
   207  }
   208  
   209  func TestGetDuties_SyncNotReady(t *testing.T) {
   210  	vs := &Server{
   211  		SyncChecker: &mockSync.Sync{IsSyncing: true},
   212  	}
   213  	_, err := vs.GetDuties(context.Background(), &ethpb.DutiesRequest{})
   214  	assert.ErrorContains(t, "Syncing to latest head", err)
   215  }
   216  
   217  func TestStreamDuties_SyncNotReady(t *testing.T) {
   218  	vs := &Server{
   219  		SyncChecker: &mockSync.Sync{IsSyncing: true},
   220  	}
   221  	ctrl := gomock.NewController(t)
   222  	defer ctrl.Finish()
   223  	mockStream := mock.NewMockBeaconNodeValidator_StreamDutiesServer(ctrl)
   224  	assert.ErrorContains(t, "Syncing to latest head", vs.StreamDuties(&ethpb.DutiesRequest{}, mockStream))
   225  }
   226  
   227  func TestStreamDuties_OK(t *testing.T) {
   228  	db := dbutil.SetupDB(t)
   229  
   230  	genesis := testutil.NewBeaconBlock()
   231  	depChainStart := params.BeaconConfig().MinGenesisActiveValidatorCount
   232  	deposits, _, err := testutil.DeterministicDepositsAndKeys(depChainStart)
   233  	require.NoError(t, err)
   234  	eth1Data, err := testutil.DeterministicEth1Data(len(deposits))
   235  	require.NoError(t, err)
   236  	bs, err := state.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
   237  	require.NoError(t, err, "Could not setup genesis bs")
   238  	genesisRoot, err := genesis.Block.HashTreeRoot()
   239  	require.NoError(t, err, "Could not get signing root")
   240  
   241  	pubKeys := make([][]byte, len(deposits))
   242  	indices := make([]uint64, len(deposits))
   243  	for i := 0; i < len(deposits); i++ {
   244  		pubKeys[i] = deposits[i].Data.PublicKey
   245  		indices[i] = uint64(i)
   246  	}
   247  
   248  	pubkeysAs48ByteType := make([][48]byte, len(pubKeys))
   249  	for i, pk := range pubKeys {
   250  		pubkeysAs48ByteType[i] = bytesutil.ToBytes48(pk)
   251  	}
   252  
   253  	ctx, cancel := context.WithCancel(context.Background())
   254  	c := &mockChain.ChainService{
   255  		Genesis: time.Now(),
   256  	}
   257  	vs := &Server{
   258  		Ctx:           ctx,
   259  		BeaconDB:      db,
   260  		HeadFetcher:   &mockChain.ChainService{State: bs, Root: genesisRoot[:]},
   261  		SyncChecker:   &mockSync.Sync{IsSyncing: false},
   262  		TimeFetcher:   c,
   263  		StateNotifier: &mockChain.MockStateNotifier{},
   264  	}
   265  
   266  	// Test the first validator in registry.
   267  	req := &ethpb.DutiesRequest{
   268  		PublicKeys: [][]byte{deposits[0].Data.PublicKey},
   269  	}
   270  	wantedRes, err := vs.duties(ctx, req)
   271  	require.NoError(t, err)
   272  	ctrl := gomock.NewController(t)
   273  	defer ctrl.Finish()
   274  	exitRoutine := make(chan bool)
   275  	mockStream := mock.NewMockBeaconNodeValidator_StreamDutiesServer(ctrl)
   276  	mockStream.EXPECT().Send(wantedRes).Do(func(arg0 interface{}) {
   277  		exitRoutine <- true
   278  	})
   279  	mockStream.EXPECT().Context().Return(ctx).AnyTimes()
   280  	go func(tt *testing.T) {
   281  		assert.ErrorContains(t, "context canceled", vs.StreamDuties(req, mockStream))
   282  	}(t)
   283  	<-exitRoutine
   284  	cancel()
   285  }
   286  
   287  func TestStreamDuties_OK_ChainReorg(t *testing.T) {
   288  	db := dbutil.SetupDB(t)
   289  
   290  	genesis := testutil.NewBeaconBlock()
   291  	depChainStart := params.BeaconConfig().MinGenesisActiveValidatorCount
   292  	deposits, _, err := testutil.DeterministicDepositsAndKeys(depChainStart)
   293  	require.NoError(t, err)
   294  	eth1Data, err := testutil.DeterministicEth1Data(len(deposits))
   295  	require.NoError(t, err)
   296  	bs, err := state.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
   297  	require.NoError(t, err, "Could not setup genesis bs")
   298  	genesisRoot, err := genesis.Block.HashTreeRoot()
   299  	require.NoError(t, err, "Could not get signing root")
   300  
   301  	pubKeys := make([][]byte, len(deposits))
   302  	indices := make([]uint64, len(deposits))
   303  	for i := 0; i < len(deposits); i++ {
   304  		pubKeys[i] = deposits[i].Data.PublicKey
   305  		indices[i] = uint64(i)
   306  	}
   307  
   308  	pubkeysAs48ByteType := make([][48]byte, len(pubKeys))
   309  	for i, pk := range pubKeys {
   310  		pubkeysAs48ByteType[i] = bytesutil.ToBytes48(pk)
   311  	}
   312  
   313  	ctx, cancel := context.WithCancel(context.Background())
   314  	c := &mockChain.ChainService{
   315  		Genesis: time.Now(),
   316  	}
   317  	vs := &Server{
   318  		Ctx:           ctx,
   319  		BeaconDB:      db,
   320  		HeadFetcher:   &mockChain.ChainService{State: bs, Root: genesisRoot[:]},
   321  		SyncChecker:   &mockSync.Sync{IsSyncing: false},
   322  		TimeFetcher:   c,
   323  		StateNotifier: &mockChain.MockStateNotifier{},
   324  	}
   325  
   326  	// Test the first validator in registry.
   327  	req := &ethpb.DutiesRequest{
   328  		PublicKeys: [][]byte{deposits[0].Data.PublicKey},
   329  	}
   330  	wantedRes, err := vs.duties(ctx, req)
   331  	require.NoError(t, err)
   332  	ctrl := gomock.NewController(t)
   333  	defer ctrl.Finish()
   334  	exitRoutine := make(chan bool)
   335  	mockStream := mock.NewMockBeaconNodeValidator_StreamDutiesServer(ctrl)
   336  	mockStream.EXPECT().Send(wantedRes).Return(nil)
   337  	mockStream.EXPECT().Send(wantedRes).Do(func(arg0 interface{}) {
   338  		exitRoutine <- true
   339  	})
   340  	mockStream.EXPECT().Context().Return(ctx).AnyTimes()
   341  	go func(tt *testing.T) {
   342  		assert.ErrorContains(t, "context canceled", vs.StreamDuties(req, mockStream))
   343  	}(t)
   344  	// Fire a reorg event. This needs to trigger
   345  	// a recomputation and resending of duties over the stream.
   346  	for sent := 0; sent == 0; {
   347  		sent = vs.StateNotifier.StateFeed().Send(&feed.Event{
   348  			Type: statefeed.Reorg,
   349  			Data: &ethpbv1.EventChainReorg{Depth: uint64(params.BeaconConfig().SlotsPerEpoch), Slot: 0},
   350  		})
   351  	}
   352  	<-exitRoutine
   353  	cancel()
   354  }
   355  
   356  func TestAssignValidatorToSubnet(t *testing.T) {
   357  	k := pubKey(3)
   358  
   359  	assignValidatorToSubnet(k, ethpb.ValidatorStatus_ACTIVE)
   360  	coms, ok, exp := cache.SubnetIDs.GetPersistentSubnets(k)
   361  	require.Equal(t, true, ok, "No cache entry found for validator")
   362  	assert.Equal(t, params.BeaconConfig().RandomSubnetsPerValidator, uint64(len(coms)))
   363  	epochDuration := time.Duration(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
   364  	totalTime := time.Duration(params.BeaconConfig().EpochsPerRandomSubnetSubscription) * epochDuration * time.Second
   365  	receivedTime := time.Until(exp.Round(time.Second))
   366  	if receivedTime < totalTime {
   367  		t.Fatalf("Expiration time of %f was less than expected duration of %f ", receivedTime.Seconds(), totalTime.Seconds())
   368  	}
   369  }
   370  
   371  func BenchmarkCommitteeAssignment(b *testing.B) {
   372  	db := dbutil.SetupDB(b)
   373  
   374  	genesis := testutil.NewBeaconBlock()
   375  	depChainStart := uint64(8192 * 2)
   376  	deposits, _, err := testutil.DeterministicDepositsAndKeys(depChainStart)
   377  	require.NoError(b, err)
   378  	eth1Data, err := testutil.DeterministicEth1Data(len(deposits))
   379  	require.NoError(b, err)
   380  	bs, err := state.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
   381  	require.NoError(b, err, "Could not setup genesis bs")
   382  	genesisRoot, err := genesis.Block.HashTreeRoot()
   383  	require.NoError(b, err, "Could not get signing root")
   384  
   385  	pubKeys := make([][48]byte, len(deposits))
   386  	indices := make([]uint64, len(deposits))
   387  	for i := 0; i < len(deposits); i++ {
   388  		pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
   389  		indices[i] = uint64(i)
   390  	}
   391  
   392  	vs := &Server{
   393  		BeaconDB:    db,
   394  		HeadFetcher: &mockChain.ChainService{State: bs, Root: genesisRoot[:]},
   395  		SyncChecker: &mockSync.Sync{IsSyncing: false},
   396  	}
   397  
   398  	// Create request for all validators in the system.
   399  	pks := make([][]byte, len(deposits))
   400  	for i, deposit := range deposits {
   401  		pks[i] = deposit.Data.PublicKey
   402  	}
   403  	req := &ethpb.DutiesRequest{
   404  		PublicKeys: pks,
   405  		Epoch:      0,
   406  	}
   407  	b.ResetTimer()
   408  	for i := 0; i < b.N; i++ {
   409  		_, err := vs.GetDuties(context.Background(), req)
   410  		assert.NoError(b, err)
   411  	}
   412  }