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 := ðpb.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 = ðpb.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 = ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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(), ðpb.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(ðpb.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 := ðpb.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 := ðpb.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: ðpbv1.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 := ðpb.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 }