github.com/prysmaticlabs/prysm@v1.4.4/slasher/detection/attestations/spanner_test.go (about) 1 package attestations 2 3 import ( 4 "context" 5 "reflect" 6 "testing" 7 8 types "github.com/prysmaticlabs/eth2-types" 9 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 10 "github.com/prysmaticlabs/prysm/shared/featureconfig" 11 "github.com/prysmaticlabs/prysm/shared/sliceutil" 12 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 13 "github.com/prysmaticlabs/prysm/shared/testutil/require" 14 testDB "github.com/prysmaticlabs/prysm/slasher/db/testing" 15 dbtypes "github.com/prysmaticlabs/prysm/slasher/db/types" 16 slashertypes "github.com/prysmaticlabs/prysm/slasher/detection/attestations/types" 17 ) 18 19 func indexedAttestation(source, target types.Epoch, indices []uint64) *ethpb.IndexedAttestation { 20 return ðpb.IndexedAttestation{ 21 AttestingIndices: indices, 22 Data: ðpb.AttestationData{ 23 Source: ðpb.Checkpoint{ 24 Epoch: source, 25 Root: []byte("good source"), 26 }, 27 Target: ðpb.Checkpoint{ 28 Epoch: target, 29 Root: []byte("good target"), 30 }, 31 }, 32 Signature: []byte{1, 2}, 33 } 34 } 35 36 func TestSpanDetector_DetectSlashingsForAttestation_Double(t *testing.T) { 37 type testStruct struct { 38 name string 39 att *ethpb.IndexedAttestation 40 incomingAtt *ethpb.IndexedAttestation 41 slashCount int 42 } 43 tests := []testStruct{ 44 { 45 name: "att with different target root, same target epoch, should slash", 46 att: ðpb.IndexedAttestation{ 47 AttestingIndices: []uint64{1, 2}, 48 Data: ðpb.AttestationData{ 49 Source: ðpb.Checkpoint{ 50 Epoch: 0, 51 Root: []byte("good source"), 52 }, 53 Target: ðpb.Checkpoint{ 54 Epoch: 2, 55 Root: []byte("good target"), 56 }, 57 }, 58 Signature: []byte{1, 2}, 59 }, 60 incomingAtt: ðpb.IndexedAttestation{ 61 AttestingIndices: []uint64{2}, 62 Data: ðpb.AttestationData{ 63 Source: ðpb.Checkpoint{ 64 Epoch: 0, 65 Root: []byte("good source"), 66 }, 67 Target: ðpb.Checkpoint{ 68 Epoch: 2, 69 Root: []byte("bad target"), 70 }, 71 }, 72 }, 73 slashCount: 1, 74 }, 75 { 76 name: "att with different source, same target, should slash", 77 att: ðpb.IndexedAttestation{ 78 AttestingIndices: []uint64{1, 2}, 79 Data: ðpb.AttestationData{ 80 Source: ðpb.Checkpoint{ 81 Epoch: 0, 82 Root: []byte("good source"), 83 }, 84 Target: ðpb.Checkpoint{ 85 Epoch: 2, 86 Root: []byte("good target"), 87 }, 88 }, 89 Signature: []byte{1, 2}, 90 }, 91 incomingAtt: ðpb.IndexedAttestation{ 92 AttestingIndices: []uint64{2}, 93 Data: ðpb.AttestationData{ 94 Source: ðpb.Checkpoint{ 95 Epoch: 1, 96 Root: []byte("bad source"), 97 }, 98 Target: ðpb.Checkpoint{ 99 Epoch: 2, 100 Root: []byte("bad target"), 101 }, 102 }, 103 }, 104 slashCount: 1, 105 }, 106 { 107 name: "att with different committee index, rest is the same, should slash", 108 att: ðpb.IndexedAttestation{ 109 AttestingIndices: []uint64{1, 2}, 110 Data: ðpb.AttestationData{ 111 CommitteeIndex: 4, 112 Source: ðpb.Checkpoint{ 113 Epoch: 0, 114 Root: []byte("good source"), 115 }, 116 Target: ðpb.Checkpoint{ 117 Epoch: 2, 118 Root: []byte("good target"), 119 }, 120 }, 121 Signature: []byte{1, 2}, 122 }, 123 incomingAtt: ðpb.IndexedAttestation{ 124 AttestingIndices: []uint64{2}, 125 Data: ðpb.AttestationData{ 126 CommitteeIndex: 3, 127 Source: ðpb.Checkpoint{ 128 Epoch: 1, 129 Root: []byte("bad source"), 130 }, 131 Target: ðpb.Checkpoint{ 132 Epoch: 2, 133 Root: []byte("bad target"), 134 }, 135 }, 136 Signature: []byte{1, 2}, 137 }, 138 slashCount: 1, 139 }, 140 { 141 name: "att with same target and source, different block root, should slash", 142 att: ðpb.IndexedAttestation{ 143 AttestingIndices: []uint64{1, 2, 4, 6}, 144 Data: ðpb.AttestationData{ 145 Source: ðpb.Checkpoint{ 146 Epoch: 0, 147 Root: []byte("good source"), 148 }, 149 Target: ðpb.Checkpoint{ 150 Epoch: 2, 151 Root: []byte("good target"), 152 }, 153 BeaconBlockRoot: []byte("good block root"), 154 }, 155 Signature: []byte{1, 2}, 156 }, 157 incomingAtt: ðpb.IndexedAttestation{ 158 AttestingIndices: []uint64{2, 4, 6}, 159 Data: ðpb.AttestationData{ 160 Source: ðpb.Checkpoint{ 161 Epoch: 0, 162 Root: []byte("good source"), 163 }, 164 Target: ðpb.Checkpoint{ 165 Epoch: 2, 166 Root: []byte("good target"), 167 }, 168 BeaconBlockRoot: []byte("bad block root"), 169 }, 170 }, 171 slashCount: 3, 172 }, 173 { 174 name: "att with different target, should not detect possible double", 175 att: ðpb.IndexedAttestation{ 176 AttestingIndices: []uint64{1, 2, 4, 6}, 177 Data: ðpb.AttestationData{ 178 Source: ðpb.Checkpoint{ 179 Epoch: 0, 180 Root: []byte("good source"), 181 }, 182 Target: ðpb.Checkpoint{ 183 Epoch: 2, 184 Root: []byte("good target"), 185 }, 186 BeaconBlockRoot: []byte("good block root"), 187 }, 188 Signature: []byte{1, 2}, 189 }, 190 incomingAtt: ðpb.IndexedAttestation{ 191 AttestingIndices: []uint64{2, 4, 6}, 192 Data: ðpb.AttestationData{ 193 Source: ðpb.Checkpoint{ 194 Epoch: 0, 195 Root: []byte("good source"), 196 }, 197 Target: ðpb.Checkpoint{ 198 Epoch: 1, 199 Root: []byte("really good target"), 200 }, 201 BeaconBlockRoot: []byte("really good block root"), 202 }, 203 }, 204 slashCount: 0, 205 }, 206 { 207 name: "same att with different aggregates, should detect possible double", 208 att: ðpb.IndexedAttestation{ 209 AttestingIndices: []uint64{1, 2, 4, 6}, 210 Data: ðpb.AttestationData{ 211 Source: ðpb.Checkpoint{ 212 Epoch: 0, 213 Root: []byte("good source"), 214 }, 215 Target: ðpb.Checkpoint{ 216 Epoch: 2, 217 Root: []byte("good target"), 218 }, 219 BeaconBlockRoot: []byte("good block root"), 220 }, 221 Signature: []byte{1, 2}, 222 }, 223 incomingAtt: ðpb.IndexedAttestation{ 224 AttestingIndices: []uint64{2, 3, 4, 16}, 225 Data: ðpb.AttestationData{ 226 Source: ðpb.Checkpoint{ 227 Epoch: 0, 228 Root: []byte("good source"), 229 }, 230 Target: ðpb.Checkpoint{ 231 Epoch: 2, 232 Root: []byte("good target"), 233 }, 234 BeaconBlockRoot: []byte("good block root"), 235 }, 236 }, 237 slashCount: 2, 238 }, 239 } 240 for _, tt := range tests { 241 t.Run(tt.name, func(t *testing.T) { 242 db := testDB.SetupSlasherDB(t, false) 243 ctx := context.Background() 244 245 sd := &SpanDetector{ 246 slasherDB: db, 247 } 248 249 require.NoError(t, sd.UpdateSpans(ctx, tt.att)) 250 251 res, err := sd.DetectSlashingsForAttestation(ctx, tt.incomingAtt) 252 require.NoError(t, err) 253 254 var want []*slashertypes.DetectionResult 255 if tt.slashCount > 0 { 256 for _, indice := range sliceutil.IntersectionUint64(tt.att.AttestingIndices, tt.incomingAtt.AttestingIndices) { 257 want = append(want, &slashertypes.DetectionResult{ 258 ValidatorIndex: indice, 259 Kind: slashertypes.DoubleVote, 260 SlashableEpoch: tt.incomingAtt.Data.Target.Epoch, 261 SigBytes: [2]byte{1, 2}, 262 }) 263 } 264 } 265 assert.DeepEqual(t, want, res) 266 require.Equal(t, tt.slashCount, len(res), "Unexpected amount of slashings found") 267 }) 268 } 269 } 270 271 func TestSpanDetector_DetectSlashingsForAttestation_Surround(t *testing.T) { 272 type testStruct struct { 273 name string 274 sourceEpoch types.Epoch 275 targetEpoch types.Epoch 276 slashableEpoch types.Epoch 277 shouldSlash bool 278 spansByEpochForValidator map[types.Epoch][3]uint16 279 } 280 tests := []testStruct{ 281 { 282 name: "Should slash if max span > distance", 283 sourceEpoch: 3, 284 targetEpoch: 6, 285 slashableEpoch: 7, 286 shouldSlash: true, 287 // Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to have 288 // committed a slashable offense by having a max span of 4 > distance. 289 spansByEpochForValidator: map[types.Epoch][3]uint16{ 290 3: {0, 4}, 291 }, 292 }, 293 { 294 name: "Should NOT slash if max span < distance", 295 sourceEpoch: 3, 296 targetEpoch: 6, 297 // Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to NOT 298 // have committed slashable offense by having a max span of 1 < distance. 299 shouldSlash: false, 300 spansByEpochForValidator: map[types.Epoch][3]uint16{ 301 3: {0, 1}, 302 }, 303 }, 304 { 305 name: "Should NOT slash if max span == distance", 306 sourceEpoch: 3, 307 targetEpoch: 6, 308 // Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to NOT 309 // have committed slashable offense by having a max span of 3 == distance. 310 shouldSlash: false, 311 spansByEpochForValidator: map[types.Epoch][3]uint16{ 312 3: {0, 3}, 313 }, 314 }, 315 { 316 name: "Should NOT slash if min span == 0", 317 sourceEpoch: 3, 318 targetEpoch: 6, 319 // Given a min span of 0 and no max span slashing, we want validator to NOT 320 // have committed a slashable offense if min span == 0. 321 shouldSlash: false, 322 spansByEpochForValidator: map[types.Epoch][3]uint16{ 323 3: {0, 1}, 324 }, 325 }, 326 { 327 name: "Should slash if min span > 0 and min span < distance", 328 sourceEpoch: 3, 329 targetEpoch: 6, 330 // Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to have 331 // committed a slashable offense by having a min span of 1 < distance. 332 shouldSlash: true, 333 slashableEpoch: 4, 334 spansByEpochForValidator: map[types.Epoch][3]uint16{ 335 3: {1, 0}, 336 }, 337 }, 338 // Proto Max Span Tests from the eth2-surround repo. 339 { 340 name: "Proto max span test #1", 341 sourceEpoch: 8, 342 targetEpoch: 18, 343 shouldSlash: false, 344 spansByEpochForValidator: map[types.Epoch][3]uint16{ 345 0: {4, 0}, 346 1: {2, 0}, 347 2: {1, 0}, 348 4: {0, 2}, 349 5: {0, 1}, 350 }, 351 }, 352 { 353 name: "Proto max span test #2", 354 sourceEpoch: 4, 355 targetEpoch: 12, 356 shouldSlash: false, 357 slashableEpoch: 0, 358 spansByEpochForValidator: map[types.Epoch][3]uint16{ 359 4: {14, 2}, 360 5: {13, 1}, 361 6: {12, 0}, 362 7: {11, 0}, 363 9: {0, 9}, 364 10: {0, 8}, 365 11: {0, 7}, 366 12: {0, 6}, 367 13: {0, 5}, 368 14: {0, 4}, 369 15: {0, 3}, 370 16: {0, 2}, 371 17: {0, 1}, 372 }, 373 }, 374 { 375 name: "Proto max span test #3", 376 sourceEpoch: 10, 377 targetEpoch: 15, 378 shouldSlash: true, 379 slashableEpoch: 18, 380 spansByEpochForValidator: map[types.Epoch][3]uint16{ 381 4: {14, 2}, 382 5: {13, 7}, 383 6: {12, 6}, 384 7: {11, 5}, 385 8: {0, 4}, 386 9: {0, 9}, 387 10: {0, 8}, 388 11: {0, 7}, 389 12: {0, 6}, 390 13: {0, 5}, 391 14: {0, 4}, 392 15: {0, 3}, 393 16: {0, 2}, 394 17: {0, 1}, 395 }, 396 }, 397 // Proto Min Span Tests from the eth2-surround repo. 398 { 399 name: "Proto min span test #1", 400 sourceEpoch: 4, 401 targetEpoch: 6, 402 shouldSlash: false, 403 spansByEpochForValidator: map[types.Epoch][3]uint16{ 404 1: {5, 0}, 405 2: {4, 0}, 406 3: {3, 0}, 407 }, 408 }, 409 { 410 name: "Proto min span test #2", 411 sourceEpoch: 11, 412 targetEpoch: 15, 413 shouldSlash: false, 414 spansByEpochForValidator: map[types.Epoch][3]uint16{ 415 1: {5, 0}, 416 2: {4, 0}, 417 3: {3, 0}, 418 4: {14, 0}, 419 5: {13, 1}, 420 6: {12, 0}, 421 7: {11, 0}, 422 8: {10, 0}, 423 9: {9, 0}, 424 10: {8, 0}, 425 11: {7, 0}, 426 12: {6, 0}, 427 14: {0, 4}, 428 15: {0, 3}, 429 16: {0, 2}, 430 17: {0, 1}, 431 }, 432 }, 433 { 434 name: "Proto min span test #3", 435 sourceEpoch: 9, 436 targetEpoch: 19, 437 shouldSlash: true, 438 slashableEpoch: 14, 439 spansByEpochForValidator: map[types.Epoch][3]uint16{ 440 0: {5, 0}, 441 1: {4, 0}, 442 2: {3, 0}, 443 3: {11, 0}, 444 4: {10, 1}, 445 5: {9, 0}, 446 6: {8, 0}, 447 7: {7, 0}, 448 8: {6, 0}, 449 9: {5, 0}, 450 10: {7, 0}, 451 11: {6, 3}, 452 12: {0, 2}, 453 13: {0, 1}, 454 14: {0, 3}, 455 15: {0, 2}, 456 16: {0, 1}, 457 17: {0, 0}, 458 }, 459 }, 460 { 461 name: "Should slash if max span > distance && source > target", 462 sourceEpoch: 6, 463 targetEpoch: 3, 464 slashableEpoch: 7, 465 shouldSlash: true, 466 // Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to have 467 // committed a slashable offense by having a max span of 4 > distance. 468 spansByEpochForValidator: map[types.Epoch][3]uint16{ 469 3: {0, 4}, 470 }, 471 }, 472 { 473 name: "Should not slash if max span = distance && source > target", 474 sourceEpoch: 6, 475 targetEpoch: 3, 476 shouldSlash: false, 477 // Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to NOT 478 // have committed slashable offense by having a max span of 1 < distance. 479 spansByEpochForValidator: map[types.Epoch][3]uint16{ 480 3: {0, 1}, 481 }, 482 }, 483 } 484 for _, tt := range tests { 485 t.Run(tt.name, func(t *testing.T) { 486 db := testDB.SetupSlasherDB(t, false) 487 ctx := context.Background() 488 489 sd := &SpanDetector{ 490 slasherDB: db, 491 } 492 // We only care about validator index 0 for these tests for simplicity. 493 validatorIndex := uint64(0) 494 for k, v := range tt.spansByEpochForValidator { 495 epochStore, err := slashertypes.NewEpochStore([]byte{}) 496 require.NoError(t, err) 497 span := slashertypes.Span{ 498 MinSpan: v[0], 499 MaxSpan: v[1], 500 } 501 epochStore, err = epochStore.SetValidatorSpan(validatorIndex, span) 502 require.NoError(t, err) 503 require.NoError(t, sd.slasherDB.SaveEpochSpans(ctx, k, epochStore, dbtypes.UseDB), "Failed to save to slasherDB") 504 } 505 506 att := ðpb.IndexedAttestation{ 507 Data: ðpb.AttestationData{ 508 Source: ðpb.Checkpoint{ 509 Epoch: tt.sourceEpoch, 510 }, 511 Target: ðpb.Checkpoint{ 512 Epoch: tt.targetEpoch, 513 }, 514 }, 515 AttestingIndices: []uint64{0}, 516 } 517 res, err := sd.DetectSlashingsForAttestation(ctx, att) 518 require.NoError(t, err) 519 require.Equal(t, false, !tt.shouldSlash && res != nil, "Did not want validator to be slashed but found slashable offense: %v", res) 520 if tt.shouldSlash { 521 want := []*slashertypes.DetectionResult{ 522 { 523 Kind: slashertypes.SurroundVote, 524 SlashableEpoch: tt.slashableEpoch, 525 }, 526 } 527 assert.DeepEqual(t, want, res) 528 } 529 }) 530 } 531 } 532 533 func TestSpanDetector_DetectSlashingsForAttestation_MultipleValidators(t *testing.T) { 534 type testStruct struct { 535 name string 536 incomingAtt *ethpb.IndexedAttestation 537 slashableEpochs []types.Epoch 538 shouldSlash []bool 539 atts []*ethpb.IndexedAttestation 540 } 541 tests := []testStruct{ 542 { 543 name: "3 of 4 validators slashed, differing histories", 544 incomingAtt: ðpb.IndexedAttestation{ 545 AttestingIndices: []uint64{0, 1, 2, 3}, 546 Data: ðpb.AttestationData{ 547 Source: ðpb.Checkpoint{ 548 Epoch: 3, 549 Root: []byte("good source"), 550 }, 551 Target: ðpb.Checkpoint{ 552 Epoch: 6, 553 Root: []byte("good target"), 554 }, 555 }, 556 Signature: []byte{1, 2}, 557 }, 558 slashableEpochs: []types.Epoch{6, 7, 5, 0}, 559 // Detections - double, surround, surrounded, none. 560 shouldSlash: []bool{true, true, true, false}, 561 // Atts in map: (src, epoch) - 0: (3, 6), 1: (2, 7), 2: (4, 5), 3: (5, 7) 562 atts: []*ethpb.IndexedAttestation{ 563 indexedAttestation(3, 6, []uint64{0}), 564 indexedAttestation(2, 7, []uint64{1}), 565 indexedAttestation(4, 5, []uint64{2}), 566 indexedAttestation(5, 7, []uint64{3}), 567 }, 568 }, 569 { 570 name: "3 of 4 validators slashed, differing surrounds", 571 incomingAtt: ðpb.IndexedAttestation{ 572 AttestingIndices: []uint64{0, 1, 2, 3}, 573 Data: ðpb.AttestationData{ 574 Source: ðpb.Checkpoint{ 575 Epoch: 5, 576 Root: []byte("good source"), 577 }, 578 Target: ðpb.Checkpoint{ 579 Epoch: 7, 580 Root: []byte("good target"), 581 }, 582 }, 583 Signature: []byte{1, 2}, 584 }, 585 slashableEpochs: []types.Epoch{8, 9, 10, 0}, 586 // Detections - surround, surround, surround, none. 587 shouldSlash: []bool{true, true, true, false}, 588 // Atts in map: (src, epoch) - 0: (1, 8), 1: (3, 9), 2: (2, 10), 3: (4, 6) 589 atts: []*ethpb.IndexedAttestation{ 590 indexedAttestation(1, 8, []uint64{0}), 591 indexedAttestation(3, 9, []uint64{1}), 592 indexedAttestation(2, 10, []uint64{2}), 593 indexedAttestation(4, 6, []uint64{3}), 594 }, 595 }, 596 { 597 name: "3 of 4 validators slashed, differing surrounded", 598 incomingAtt: ðpb.IndexedAttestation{ 599 AttestingIndices: []uint64{0, 1, 2, 3}, 600 Data: ðpb.AttestationData{ 601 Source: ðpb.Checkpoint{ 602 Epoch: 2, 603 Root: []byte("good source"), 604 }, 605 Target: ðpb.Checkpoint{ 606 Epoch: 9, 607 Root: []byte("good target"), 608 }, 609 }, 610 Signature: []byte{1, 2}, 611 }, 612 slashableEpochs: []types.Epoch{8, 8, 7, 0}, 613 // Detections - surround, surround, surround, none. 614 shouldSlash: []bool{true, true, true, false}, 615 // Atts in map: (src, epoch) - 0: (5, 8), 1: (3, 8), 2: (4, 7), 3: (1, 5) 616 atts: []*ethpb.IndexedAttestation{ 617 indexedAttestation(5, 8, []uint64{0}), 618 indexedAttestation(3, 8, []uint64{1}), 619 indexedAttestation(4, 7, []uint64{2}), 620 indexedAttestation(1, 5, []uint64{3}), 621 }, 622 }, 623 { 624 name: "3 of 4 validators slashed, differing doubles", 625 incomingAtt: ðpb.IndexedAttestation{ 626 AttestingIndices: []uint64{0, 1, 2, 3}, 627 Data: ðpb.AttestationData{ 628 Source: ðpb.Checkpoint{ 629 Epoch: 2, 630 Root: []byte("good source"), 631 }, 632 Target: ðpb.Checkpoint{ 633 Epoch: 7, 634 Root: []byte("good target"), 635 }, 636 }, 637 Signature: []byte{1, 2}, 638 }, 639 slashableEpochs: []types.Epoch{7, 7, 7, 0}, 640 // Detections - surround, surround, surround, none. 641 shouldSlash: []bool{true, true, true, false}, 642 // Atts in map: (src, epoch) - 0: (2, 7), 1: (3, 7), 2: (6, 7), 3: (1, 5) 643 atts: []*ethpb.IndexedAttestation{ 644 indexedAttestation(2, 7, []uint64{0}), 645 indexedAttestation(3, 7, []uint64{1}), 646 indexedAttestation(6, 7, []uint64{2}), 647 indexedAttestation(1, 5, []uint64{3}), 648 }, 649 }, 650 { 651 name: "3 of 4 validators slashed, differing surrounds source > target", 652 incomingAtt: ðpb.IndexedAttestation{ 653 AttestingIndices: []uint64{0, 1, 2, 3}, 654 Data: ðpb.AttestationData{ 655 Source: ðpb.Checkpoint{ 656 Epoch: 7, 657 Root: []byte("good source"), 658 }, 659 Target: ðpb.Checkpoint{ 660 Epoch: 5, 661 Root: []byte("good target"), 662 }, 663 }, 664 Signature: []byte{1, 2}, 665 }, 666 slashableEpochs: []types.Epoch{8, 9, 10, 0}, 667 // Detections - surround, surround, surround, none. 668 shouldSlash: []bool{true, true, true, false}, 669 // Atts in map: (src, epoch) - 0: (1, 8), 1: (3, 9), 2: (2, 10), 3: (4, 6) 670 atts: []*ethpb.IndexedAttestation{ 671 indexedAttestation(1, 8, []uint64{0}), 672 indexedAttestation(3, 9, []uint64{1}), 673 indexedAttestation(2, 10, []uint64{2}), 674 indexedAttestation(4, 6, []uint64{3}), 675 }, 676 }, 677 { 678 name: "3 of 4 validators slashed, differing surrounded source > target", 679 incomingAtt: ðpb.IndexedAttestation{ 680 AttestingIndices: []uint64{0, 1, 2, 3}, 681 Data: ðpb.AttestationData{ 682 Source: ðpb.Checkpoint{ 683 Epoch: 9, 684 Root: []byte("good source"), 685 }, 686 Target: ðpb.Checkpoint{ 687 Epoch: 2, 688 Root: []byte("good target"), 689 }, 690 }, 691 Signature: []byte{1, 2}, 692 }, 693 slashableEpochs: []types.Epoch{8, 8, 7, 0}, 694 // Detections - surround, surround, surround, none. 695 shouldSlash: []bool{true, true, true, false}, 696 // Atts in map: (src, epoch) - 0: (5, 8), 1: (3, 8), 2: (4, 7), 3: (1, 5) 697 atts: []*ethpb.IndexedAttestation{ 698 indexedAttestation(5, 8, []uint64{0}), 699 indexedAttestation(3, 8, []uint64{1}), 700 indexedAttestation(4, 7, []uint64{2}), 701 indexedAttestation(1, 5, []uint64{3}), 702 }, 703 }, 704 { 705 name: "3 of 4 validators slashed, differing surrounded source > target in update", 706 incomingAtt: ðpb.IndexedAttestation{ 707 AttestingIndices: []uint64{0, 1, 2, 3}, 708 Data: ðpb.AttestationData{ 709 Source: ðpb.Checkpoint{ 710 Epoch: 2, 711 Root: []byte("good source"), 712 }, 713 Target: ðpb.Checkpoint{ 714 Epoch: 9, 715 Root: []byte("good target"), 716 }, 717 }, 718 Signature: []byte{1, 2}, 719 }, 720 slashableEpochs: []types.Epoch{8, 8, 7, 0}, 721 // Detections - surround, surround, surround, none. 722 shouldSlash: []bool{true, true, true, false}, 723 // Atts in map: (src, epoch) - 0: (5, 8), 1: (3, 8), 2: (4, 7), 3: (1, 5) 724 atts: []*ethpb.IndexedAttestation{ 725 indexedAttestation(8, 5, []uint64{0}), 726 indexedAttestation(8, 3, []uint64{1}), 727 indexedAttestation(7, 4, []uint64{2}), 728 indexedAttestation(5, 1, []uint64{3}), 729 }, 730 }, 731 } 732 for _, tt := range tests { 733 t.Run(tt.name, func(t *testing.T) { 734 db := testDB.SetupSlasherDB(t, false) 735 ctx := context.Background() 736 defer func() { 737 assert.NoError(t, db.ClearDB()) 738 }() 739 defer func() { 740 assert.NoError(t, db.Close()) 741 }() 742 743 spanDetector := &SpanDetector{ 744 slasherDB: db, 745 } 746 for _, att := range tt.atts { 747 require.NoError(t, spanDetector.UpdateSpans(ctx, att), "Failed to save to slasherDB") 748 } 749 res, err := spanDetector.DetectSlashingsForAttestation(ctx, tt.incomingAtt) 750 require.NoError(t, err) 751 var want []*slashertypes.DetectionResult 752 for i := 0; i < len(tt.incomingAtt.AttestingIndices); i++ { 753 if tt.shouldSlash[i] { 754 if tt.slashableEpochs[i] == tt.incomingAtt.Data.Target.Epoch { 755 want = append(want, &slashertypes.DetectionResult{ 756 ValidatorIndex: uint64(i), 757 Kind: slashertypes.DoubleVote, 758 SlashableEpoch: tt.slashableEpochs[i], 759 SigBytes: [2]byte{1, 2}, 760 }) 761 } else { 762 want = append(want, &slashertypes.DetectionResult{ 763 ValidatorIndex: uint64(i), 764 Kind: slashertypes.SurroundVote, 765 SlashableEpoch: tt.slashableEpochs[i], 766 SigBytes: [2]byte{1, 2}, 767 }) 768 } 769 } 770 } 771 if !reflect.DeepEqual(want, res) { 772 for i, ww := range want { 773 t.Errorf("Wanted %d: %+v\n", i, ww) 774 } 775 for i, rr := range res { 776 t.Errorf("Received %d: %+v\n", i, rr) 777 } 778 t.Errorf("Wanted: %v, received %v", want, res) 779 } 780 }) 781 } 782 } 783 784 func TestNewSpanDetector_UpdateSpans(t *testing.T) { 785 type testStruct struct { 786 name string 787 att *ethpb.IndexedAttestation 788 want []map[uint64]slashertypes.Span 789 } 790 tests := []testStruct{ 791 { 792 name: "Distance of 2 should update min spans accordingly", 793 att: ðpb.IndexedAttestation{ 794 AttestingIndices: []uint64{0, 1, 2}, 795 Data: ðpb.AttestationData{ 796 CommitteeIndex: 0, 797 Source: ðpb.Checkpoint{ 798 Epoch: 2, 799 }, 800 Target: ðpb.Checkpoint{ 801 Epoch: 4, 802 }, 803 }, 804 Signature: []byte{1, 2}, 805 }, 806 want: []map[uint64]slashertypes.Span{ 807 // Epoch 0. 808 { 809 0: {MinSpan: 4, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false}, 810 1: {MinSpan: 4, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false}, 811 2: {MinSpan: 4, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false}, 812 }, 813 // Epoch 1. 814 { 815 0: {MinSpan: 3, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false}, 816 1: {MinSpan: 3, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false}, 817 2: {MinSpan: 3, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false}, 818 }, 819 // Epoch 2. 820 {}, 821 // Epoch 3. 822 { 823 0: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false}, 824 1: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false}, 825 2: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false}, 826 }, 827 // Epoch 4. 828 { 829 0: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true}, 830 1: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true}, 831 2: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true}, 832 }, 833 {}, 834 {}, 835 {}, 836 }, 837 }, 838 { 839 name: "Distance of 4 should update max spans accordingly", 840 att: ðpb.IndexedAttestation{ 841 AttestingIndices: []uint64{0, 1, 2}, 842 Data: ðpb.AttestationData{ 843 CommitteeIndex: 1, 844 Source: ðpb.Checkpoint{ 845 Epoch: 0, 846 }, 847 Target: ðpb.Checkpoint{ 848 Epoch: 5, 849 }, 850 }, 851 Signature: []byte{1, 2}, 852 }, 853 want: []map[uint64]slashertypes.Span{ 854 // Epoch 0. 855 {}, 856 // Epoch 1. 857 { 858 0: {MinSpan: 0, MaxSpan: 4, SigBytes: [2]byte{0, 0}, HasAttested: false}, 859 1: {MinSpan: 0, MaxSpan: 4, SigBytes: [2]byte{0, 0}, HasAttested: false}, 860 2: {MinSpan: 0, MaxSpan: 4, SigBytes: [2]byte{0, 0}, HasAttested: false}, 861 }, 862 // Epoch 2. 863 { 864 0: {MinSpan: 0, MaxSpan: 3, SigBytes: [2]byte{0, 0}, HasAttested: false}, 865 1: {MinSpan: 0, MaxSpan: 3, SigBytes: [2]byte{0, 0}, HasAttested: false}, 866 2: {MinSpan: 0, MaxSpan: 3, SigBytes: [2]byte{0, 0}, HasAttested: false}, 867 }, 868 // Epoch 3. 869 { 870 0: {MinSpan: 0, MaxSpan: 2, SigBytes: [2]byte{0, 0}, HasAttested: false}, 871 1: {MinSpan: 0, MaxSpan: 2, SigBytes: [2]byte{0, 0}, HasAttested: false}, 872 2: {MinSpan: 0, MaxSpan: 2, SigBytes: [2]byte{0, 0}, HasAttested: false}, 873 }, 874 // Epoch 4. 875 { 876 0: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false}, 877 1: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false}, 878 2: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false}, 879 }, 880 // Epoch 5. 881 { 882 0: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true}, 883 1: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true}, 884 2: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true}, 885 }, 886 {}, 887 {}, 888 }, 889 }, 890 } 891 for _, tt := range tests { 892 t.Run(tt.name, func(t *testing.T) { 893 db := testDB.SetupSlasherDB(t, false) 894 ctx := context.Background() 895 defer func() { 896 assert.NoError(t, db.ClearDB()) 897 }() 898 defer func() { 899 assert.NoError(t, db.Close()) 900 }() 901 902 sd := &SpanDetector{ 903 slasherDB: db, 904 } 905 require.NoError(t, sd.UpdateSpans(ctx, tt.att)) 906 for epoch := range tt.want { 907 sm, err := sd.slasherDB.EpochSpans(ctx, types.Epoch(epoch), dbtypes.UseDB) 908 require.NoError(t, err, "Failed to read from slasherDB") 909 resMap, err := sm.ToMap() 910 require.NoError(t, err) 911 assert.DeepEqual(t, tt.want[epoch], resMap) 912 } 913 }) 914 } 915 } 916 917 func TestSpanDetector_UpdateMinSpansCheckCacheSize(t *testing.T) { 918 resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{DisableLookback: true}) 919 defer resetCfg() 920 921 att := ðpb.IndexedAttestation{ 922 AttestingIndices: []uint64{0}, 923 Data: ðpb.AttestationData{ 924 CommitteeIndex: 0, 925 Source: ðpb.Checkpoint{ 926 Epoch: 150, 927 }, 928 Target: ðpb.Checkpoint{ 929 Epoch: 152, 930 }, 931 }, 932 Signature: []byte{1, 2}, 933 } 934 935 db := testDB.SetupSlasherDB(t, false) 936 ctx := context.Background() 937 defer func() { 938 assert.NoError(t, db.ClearDB()) 939 }() 940 defer func() { 941 assert.NoError(t, db.Close()) 942 }() 943 944 sd := &SpanDetector{ 945 slasherDB: db, 946 } 947 require.NoError(t, sd.updateMinSpan(ctx, att)) 948 require.Equal(t, int(epochLookback), db.CacheLength(ctx), "Unexpected cache length") 949 }