github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/db/slasherkv/slasher_test.go (about) 1 package slasherkv 2 3 import ( 4 "context" 5 "encoding/binary" 6 "reflect" 7 "testing" 8 9 ssz "github.com/ferranbt/fastssz" 10 types "github.com/prysmaticlabs/eth2-types" 11 slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types" 12 slashpb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" 13 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 14 "github.com/prysmaticlabs/prysm/shared/bytesutil" 15 "github.com/prysmaticlabs/prysm/shared/params" 16 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 17 "github.com/prysmaticlabs/prysm/shared/testutil/require" 18 ) 19 20 func TestStore_AttestationRecordForValidator_SaveRetrieve(t *testing.T) { 21 ctx := context.Background() 22 beaconDB := setupDB(t) 23 valIdx := types.ValidatorIndex(1) 24 target := types.Epoch(5) 25 source := types.Epoch(4) 26 attRecord, err := beaconDB.AttestationRecordForValidator(ctx, valIdx, target) 27 require.NoError(t, err) 28 require.Equal(t, true, attRecord == nil) 29 30 sr := [32]byte{1} 31 err = beaconDB.SaveAttestationRecordsForValidators( 32 ctx, 33 []*slashertypes.IndexedAttestationWrapper{ 34 createAttestationWrapper(source, target, []uint64{uint64(valIdx)}, sr[:]), 35 }, 36 ) 37 require.NoError(t, err) 38 attRecord, err = beaconDB.AttestationRecordForValidator(ctx, valIdx, target) 39 require.NoError(t, err) 40 assert.DeepEqual(t, target, attRecord.IndexedAttestation.Data.Target.Epoch) 41 assert.DeepEqual(t, source, attRecord.IndexedAttestation.Data.Source.Epoch) 42 assert.DeepEqual(t, sr, attRecord.SigningRoot) 43 } 44 45 func TestStore_LastEpochWrittenForValidators(t *testing.T) { 46 ctx := context.Background() 47 beaconDB := setupDB(t) 48 indices := []types.ValidatorIndex{1, 2, 3} 49 epoch := types.Epoch(5) 50 51 attestedEpochs, err := beaconDB.LastEpochWrittenForValidators(ctx, indices) 52 require.NoError(t, err) 53 require.Equal(t, true, len(attestedEpochs) == 0) 54 55 err = beaconDB.SaveLastEpochWrittenForValidators(ctx, indices, epoch) 56 require.NoError(t, err) 57 58 retrievedEpochs, err := beaconDB.LastEpochWrittenForValidators(ctx, indices) 59 require.NoError(t, err) 60 require.Equal(t, len(indices), len(retrievedEpochs)) 61 62 for i, retrievedEpoch := range retrievedEpochs { 63 want := &slashertypes.AttestedEpochForValidator{ 64 Epoch: epoch, 65 ValidatorIndex: indices[i], 66 } 67 require.DeepEqual(t, want, retrievedEpoch) 68 } 69 } 70 71 func TestStore_CheckAttesterDoubleVotes(t *testing.T) { 72 ctx := context.Background() 73 beaconDB := setupDB(t) 74 err := beaconDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{ 75 createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), 76 createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{1}), 77 }) 78 require.NoError(t, err) 79 80 slashableAtts := []*slashertypes.IndexedAttestationWrapper{ 81 createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), // Different signing root. 82 createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{2}), // Different signing root. 83 } 84 85 wanted := []*slashertypes.AttesterDoubleVote{ 86 { 87 ValidatorIndex: 0, 88 Target: 3, 89 PrevAttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), 90 AttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), 91 }, 92 { 93 ValidatorIndex: 1, 94 Target: 3, 95 PrevAttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), 96 AttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), 97 }, 98 { 99 ValidatorIndex: 2, 100 Target: 4, 101 PrevAttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{1}), 102 AttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{2}), 103 }, 104 { 105 ValidatorIndex: 3, 106 Target: 4, 107 PrevAttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{1}), 108 AttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{2}), 109 }, 110 } 111 doubleVotes, err := beaconDB.CheckAttesterDoubleVotes(ctx, slashableAtts) 112 require.NoError(t, err) 113 require.DeepEqual(t, wanted, doubleVotes) 114 } 115 116 func TestStore_SlasherChunk_SaveRetrieve(t *testing.T) { 117 ctx := context.Background() 118 beaconDB := setupDB(t) 119 elemsPerChunk := 16 120 totalChunks := 64 121 chunkKeys := make([][]byte, totalChunks) 122 chunks := make([][]uint16, totalChunks) 123 for i := 0; i < totalChunks; i++ { 124 chunk := make([]uint16, elemsPerChunk) 125 for j := 0; j < len(chunk); j++ { 126 chunk[j] = uint16(0) 127 } 128 chunks[i] = chunk 129 chunkKeys[i] = ssz.MarshalUint64(make([]byte, 0), uint64(i)) 130 } 131 132 // We save chunks for min spans. 133 err := beaconDB.SaveSlasherChunks(ctx, slashertypes.MinSpan, chunkKeys, chunks) 134 require.NoError(t, err) 135 136 // We expect no chunks to be stored for max spans. 137 _, chunksExist, err := beaconDB.LoadSlasherChunks( 138 ctx, slashertypes.MaxSpan, chunkKeys, 139 ) 140 require.NoError(t, err) 141 require.Equal(t, len(chunks), len(chunksExist)) 142 for _, exists := range chunksExist { 143 require.Equal(t, false, exists) 144 } 145 146 // We check we saved the right chunks. 147 retrievedChunks, chunksExist, err := beaconDB.LoadSlasherChunks( 148 ctx, slashertypes.MinSpan, chunkKeys, 149 ) 150 require.NoError(t, err) 151 require.Equal(t, len(chunks), len(retrievedChunks)) 152 require.Equal(t, len(chunks), len(chunksExist)) 153 for i, exists := range chunksExist { 154 require.Equal(t, true, exists) 155 require.DeepEqual(t, chunks[i], retrievedChunks[i]) 156 } 157 158 // We save chunks for max spans. 159 err = beaconDB.SaveSlasherChunks(ctx, slashertypes.MaxSpan, chunkKeys, chunks) 160 require.NoError(t, err) 161 162 // We check we saved the right chunks. 163 retrievedChunks, chunksExist, err = beaconDB.LoadSlasherChunks( 164 ctx, slashertypes.MaxSpan, chunkKeys, 165 ) 166 require.NoError(t, err) 167 require.Equal(t, len(chunks), len(retrievedChunks)) 168 require.Equal(t, len(chunks), len(chunksExist)) 169 for i, exists := range chunksExist { 170 require.Equal(t, true, exists) 171 require.DeepEqual(t, chunks[i], retrievedChunks[i]) 172 } 173 } 174 175 func TestStore_ExistingBlockProposals(t *testing.T) { 176 ctx := context.Background() 177 beaconDB := setupDB(t) 178 proposals := []*slashertypes.SignedBlockHeaderWrapper{ 179 createProposalWrapper(t, 1, 1, []byte{1}), 180 createProposalWrapper(t, 2, 1, []byte{1}), 181 createProposalWrapper(t, 3, 1, []byte{1}), 182 } 183 // First time checking should return empty existing proposals. 184 doubleProposals, err := beaconDB.CheckDoubleBlockProposals(ctx, proposals) 185 require.NoError(t, err) 186 require.Equal(t, 0, len(doubleProposals)) 187 188 // We then save the block proposals to disk. 189 err = beaconDB.SaveBlockProposals(ctx, proposals) 190 require.NoError(t, err) 191 192 // Second time checking same proposals but all with different signing root should 193 // return all double proposals. 194 proposals[0].SigningRoot = bytesutil.ToBytes32([]byte{2}) 195 proposals[1].SigningRoot = bytesutil.ToBytes32([]byte{2}) 196 proposals[2].SigningRoot = bytesutil.ToBytes32([]byte{2}) 197 198 doubleProposals, err = beaconDB.CheckDoubleBlockProposals(ctx, proposals) 199 require.NoError(t, err) 200 require.Equal(t, len(proposals), len(doubleProposals)) 201 for i, existing := range doubleProposals { 202 require.DeepEqual(t, doubleProposals[i].Header_1, existing.Header_1) 203 } 204 } 205 206 func Test_encodeDecodeProposalRecord(t *testing.T) { 207 tests := []struct { 208 name string 209 blkHdr *slashertypes.SignedBlockHeaderWrapper 210 wantErr bool 211 }{ 212 { 213 name: "empty standard encode/decode", 214 blkHdr: createProposalWrapper(t, 0, 0, nil), 215 }, 216 { 217 name: "standard encode/decode", 218 blkHdr: createProposalWrapper(t, 15, 6, []byte("1")), 219 }, 220 { 221 name: "failing encode/decode", 222 blkHdr: &slashertypes.SignedBlockHeaderWrapper{ 223 SignedBeaconBlockHeader: ðpb.SignedBeaconBlockHeader{ 224 Header: ðpb.BeaconBlockHeader{}, 225 }, 226 }, 227 wantErr: true, 228 }, 229 { 230 name: "failing empty encode/decode", 231 blkHdr: &slashertypes.SignedBlockHeaderWrapper{}, 232 wantErr: true, 233 }, 234 { 235 name: "failing nil", 236 blkHdr: nil, 237 wantErr: true, 238 }, 239 } 240 for _, tt := range tests { 241 t.Run(tt.name, func(t *testing.T) { 242 got, err := encodeProposalRecord(tt.blkHdr) 243 if (err != nil) != tt.wantErr { 244 t.Fatalf("encodeProposalRecord() error = %v, wantErr %v", err, tt.wantErr) 245 } 246 decoded, err := decodeProposalRecord(got) 247 if (err != nil) != tt.wantErr { 248 t.Fatalf("decodeProposalRecord() error = %v, wantErr %v", err, tt.wantErr) 249 } 250 251 if !tt.wantErr && !reflect.DeepEqual(tt.blkHdr, decoded) { 252 t.Errorf("Did not match got = %v, want %v", tt.blkHdr, decoded) 253 } 254 }) 255 } 256 } 257 258 func Test_encodeDecodeAttestationRecord(t *testing.T) { 259 tests := []struct { 260 name string 261 attWrapper *slashertypes.IndexedAttestationWrapper 262 wantErr bool 263 }{ 264 { 265 name: "empty standard encode/decode", 266 attWrapper: createAttestationWrapper(0, 0, nil /* indices */, nil /* signingRoot */), 267 }, 268 { 269 name: "standard encode/decode", 270 attWrapper: createAttestationWrapper(15, 6, []uint64{2, 4}, []byte("1") /* signingRoot */), 271 }, 272 { 273 name: "failing encode/decode", 274 attWrapper: &slashertypes.IndexedAttestationWrapper{ 275 IndexedAttestation: ðpb.IndexedAttestation{ 276 Data: ðpb.AttestationData{}, 277 }, 278 }, 279 wantErr: true, 280 }, 281 { 282 name: "failing empty encode/decode", 283 attWrapper: &slashertypes.IndexedAttestationWrapper{}, 284 wantErr: true, 285 }, 286 { 287 name: "failing nil", 288 attWrapper: nil, 289 wantErr: true, 290 }, 291 } 292 for _, tt := range tests { 293 t.Run(tt.name, func(t *testing.T) { 294 got, err := encodeAttestationRecord(tt.attWrapper) 295 if (err != nil) != tt.wantErr { 296 t.Fatalf("encodeAttestationRecord() error = %v, wantErr %v", err, tt.wantErr) 297 } 298 decoded, err := decodeAttestationRecord(got) 299 if (err != nil) != tt.wantErr { 300 t.Fatalf("decodeAttestationRecord() error = %v, wantErr %v", err, tt.wantErr) 301 } 302 303 if !tt.wantErr && !reflect.DeepEqual(tt.attWrapper, decoded) { 304 t.Errorf("Did not match got = %v, want %v", tt.attWrapper, decoded) 305 } 306 }) 307 } 308 } 309 310 func TestStore_HighestAttestations(t *testing.T) { 311 ctx := context.Background() 312 tests := []struct { 313 name string 314 attestationsInDB []*slashertypes.IndexedAttestationWrapper 315 expected []*slashpb.HighestAttestation 316 indices []types.ValidatorIndex 317 wantErr bool 318 }{ 319 { 320 name: "should get highest att if single att in db", 321 attestationsInDB: []*slashertypes.IndexedAttestationWrapper{ 322 createAttestationWrapper(0, 3, []uint64{1}, []byte{1}), 323 }, 324 indices: []types.ValidatorIndex{1}, 325 expected: []*slashpb.HighestAttestation{ 326 { 327 ValidatorIndex: 1, 328 HighestSourceEpoch: 0, 329 HighestTargetEpoch: 3, 330 }, 331 }, 332 }, 333 { 334 name: "should get highest att for multiple with diff histories", 335 attestationsInDB: []*slashertypes.IndexedAttestationWrapper{ 336 createAttestationWrapper(0, 3, []uint64{2}, []byte{1}), 337 createAttestationWrapper(1, 4, []uint64{3}, []byte{1}), 338 createAttestationWrapper(2, 3, []uint64{4}, []byte{1}), 339 createAttestationWrapper(5, 6, []uint64{5}, []byte{1}), 340 }, 341 indices: []types.ValidatorIndex{2, 3, 4, 5}, 342 expected: []*slashpb.HighestAttestation{ 343 { 344 ValidatorIndex: 2, 345 HighestSourceEpoch: 0, 346 HighestTargetEpoch: 3, 347 }, 348 { 349 ValidatorIndex: 3, 350 HighestSourceEpoch: 1, 351 HighestTargetEpoch: 4, 352 }, 353 { 354 ValidatorIndex: 4, 355 HighestSourceEpoch: 2, 356 HighestTargetEpoch: 3, 357 }, 358 { 359 ValidatorIndex: 5, 360 HighestSourceEpoch: 5, 361 HighestTargetEpoch: 6, 362 }, 363 }, 364 }, 365 { 366 name: "should get correct highest att for multiple shared atts with diff histories", 367 attestationsInDB: []*slashertypes.IndexedAttestationWrapper{ 368 createAttestationWrapper(1, 4, []uint64{2, 3}, []byte{1}), 369 createAttestationWrapper(2, 5, []uint64{3, 5}, []byte{1}), 370 createAttestationWrapper(4, 5, []uint64{1, 2}, []byte{1}), 371 createAttestationWrapper(6, 7, []uint64{5}, []byte{1}), 372 }, 373 indices: []types.ValidatorIndex{2, 3, 4, 5}, 374 expected: []*slashpb.HighestAttestation{ 375 { 376 ValidatorIndex: 2, 377 HighestSourceEpoch: 4, 378 HighestTargetEpoch: 5, 379 }, 380 { 381 ValidatorIndex: 3, 382 HighestSourceEpoch: 2, 383 HighestTargetEpoch: 5, 384 }, 385 { 386 ValidatorIndex: 5, 387 HighestSourceEpoch: 6, 388 HighestTargetEpoch: 7, 389 }, 390 }, 391 }, 392 } 393 for _, tt := range tests { 394 t.Run(tt.name, func(t *testing.T) { 395 beaconDB := setupDB(t) 396 require.NoError(t, beaconDB.SaveAttestationRecordsForValidators(ctx, tt.attestationsInDB)) 397 398 highestAttestations, err := beaconDB.HighestAttestations(ctx, tt.indices) 399 require.NoError(t, err) 400 require.Equal(t, len(tt.expected), len(highestAttestations)) 401 for i, existing := range highestAttestations { 402 require.DeepEqual(t, existing, tt.expected[i]) 403 } 404 }) 405 } 406 } 407 408 func BenchmarkHighestAttestations(b *testing.B) { 409 b.StopTimer() 410 count := 10000 411 valsPerAtt := 100 412 indicesPerAtt := make([][]uint64, count) 413 for i := 0; i < count; i++ { 414 indicesForAtt := make([]uint64, valsPerAtt) 415 for r := i * count; r < valsPerAtt*(i+1); r++ { 416 indicesForAtt[i] = uint64(r) 417 } 418 indicesPerAtt[i] = indicesForAtt 419 } 420 atts := make([]*slashertypes.IndexedAttestationWrapper, count) 421 for i := 0; i < count; i++ { 422 atts[i] = createAttestationWrapper(types.Epoch(i), types.Epoch(i+2), indicesPerAtt[i], []byte{}) 423 } 424 425 ctx := context.Background() 426 beaconDB := setupDB(b) 427 require.NoError(b, beaconDB.SaveAttestationRecordsForValidators(ctx, atts)) 428 429 allIndices := make([]types.ValidatorIndex, valsPerAtt*count) 430 for i := 0; i < count; i++ { 431 indicesForAtt := make([]types.ValidatorIndex, valsPerAtt) 432 for r := 0; r < valsPerAtt; r++ { 433 indicesForAtt[r] = types.ValidatorIndex(atts[i].IndexedAttestation.AttestingIndices[r]) 434 } 435 allIndices = append(allIndices, indicesForAtt...) 436 } 437 b.ReportAllocs() 438 b.StartTimer() 439 for i := 0; i < b.N; i++ { 440 _, err := beaconDB.HighestAttestations(ctx, allIndices) 441 require.NoError(b, err) 442 } 443 } 444 445 func createProposalWrapper(t *testing.T, slot types.Slot, proposerIndex types.ValidatorIndex, signingRoot []byte) *slashertypes.SignedBlockHeaderWrapper { 446 header := ðpb.BeaconBlockHeader{ 447 Slot: slot, 448 ProposerIndex: proposerIndex, 449 ParentRoot: params.BeaconConfig().ZeroHash[:], 450 StateRoot: bytesutil.PadTo(signingRoot, 32), 451 BodyRoot: params.BeaconConfig().ZeroHash[:], 452 } 453 signRoot, err := header.HashTreeRoot() 454 if err != nil { 455 t.Fatal(err) 456 } 457 return &slashertypes.SignedBlockHeaderWrapper{ 458 SignedBeaconBlockHeader: ðpb.SignedBeaconBlockHeader{ 459 Header: header, 460 Signature: params.BeaconConfig().EmptySignature[:], 461 }, 462 SigningRoot: signRoot, 463 } 464 } 465 466 func createAttestationWrapper(source, target types.Epoch, indices []uint64, signingRoot []byte) *slashertypes.IndexedAttestationWrapper { 467 signRoot := bytesutil.ToBytes32(signingRoot) 468 if signingRoot == nil { 469 signRoot = params.BeaconConfig().ZeroHash 470 } 471 data := ðpb.AttestationData{ 472 BeaconBlockRoot: params.BeaconConfig().ZeroHash[:], 473 Source: ðpb.Checkpoint{ 474 Epoch: source, 475 Root: params.BeaconConfig().ZeroHash[:], 476 }, 477 Target: ðpb.Checkpoint{ 478 Epoch: target, 479 Root: params.BeaconConfig().ZeroHash[:], 480 }, 481 } 482 return &slashertypes.IndexedAttestationWrapper{ 483 IndexedAttestation: ðpb.IndexedAttestation{ 484 AttestingIndices: indices, 485 Data: data, 486 Signature: params.BeaconConfig().EmptySignature[:], 487 }, 488 SigningRoot: signRoot, 489 } 490 } 491 492 func Test_encodeValidatorIndex(t *testing.T) { 493 tests := []struct { 494 name string 495 index types.ValidatorIndex 496 }{ 497 { 498 name: "0", 499 index: types.ValidatorIndex(0), 500 }, 501 { 502 name: "genesis_validator_count", 503 index: types.ValidatorIndex(params.BeaconConfig().MinGenesisActiveValidatorCount), 504 }, 505 { 506 name: "max_possible_value", 507 index: types.ValidatorIndex(params.BeaconConfig().ValidatorRegistryLimit - 1), 508 }, 509 } 510 for _, tt := range tests { 511 t.Run(tt.name, func(t *testing.T) { 512 got := encodeValidatorIndex(tt.index) 513 encodedIndex := append(got[:5], 0, 0, 0) 514 decoded := binary.LittleEndian.Uint64(encodedIndex) 515 require.DeepEqual(t, tt.index, types.ValidatorIndex(decoded)) 516 }) 517 } 518 }