mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 21:57:16 +00:00
b5c4dc2a75
* init-sync updates * slasher/db/kv tests * beacon-chain/rpc/beacon tests * update kv_test * beacon-chain/rpc-validator tests updated * slasher/db/kv - remove teardown method * beacon-chain/sync tests updated * beacon-chain/db/kv tests updated * beacon-chain/blockchain tests updated * beacon-chain/state/stategen tests updated * beacon-chain/powchain updates * updates rest of slasher tests * validator/db tests * rest of the tests * minor comments update * gazelle * Merge refs/heads/master into teardowndb-to-cleanup
837 lines
22 KiB
Go
837 lines
22 KiB
Go
package attestations
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
|
|
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/shared/sliceutil"
|
|
testDB "github.com/prysmaticlabs/prysm/slasher/db/testing"
|
|
"github.com/prysmaticlabs/prysm/slasher/detection/attestations/types"
|
|
)
|
|
|
|
func indexedAttestation(source uint64, target uint64, indices []uint64) *ethpb.IndexedAttestation {
|
|
return ðpb.IndexedAttestation{
|
|
AttestingIndices: indices,
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: source,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: target,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
}
|
|
}
|
|
|
|
func TestSpanDetector_DetectSlashingsForAttestation_Double(t *testing.T) {
|
|
type testStruct struct {
|
|
name string
|
|
att *ethpb.IndexedAttestation
|
|
incomingAtt *ethpb.IndexedAttestation
|
|
slashCount uint64
|
|
}
|
|
tests := []testStruct{
|
|
{
|
|
name: "att with different target root, same target epoch, should slash",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{1, 2},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{2},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("bad target"),
|
|
},
|
|
},
|
|
},
|
|
slashCount: 1,
|
|
},
|
|
{
|
|
name: "att with different source, same target, should slash",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{1, 2},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{2},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 1,
|
|
Root: []byte("bad source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("bad target"),
|
|
},
|
|
},
|
|
},
|
|
slashCount: 1,
|
|
},
|
|
{
|
|
name: "att with different committee index, rest is the same, should slash",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{1, 2},
|
|
Data: ðpb.AttestationData{
|
|
CommitteeIndex: 4,
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{2},
|
|
Data: ðpb.AttestationData{
|
|
CommitteeIndex: 3,
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 1,
|
|
Root: []byte("bad source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("bad target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
slashCount: 1,
|
|
},
|
|
{
|
|
name: "att with same target and source, different block root, should slash",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{1, 2, 4, 6},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
BeaconBlockRoot: []byte("good block root"),
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{2, 4, 6},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
BeaconBlockRoot: []byte("bad block root"),
|
|
},
|
|
},
|
|
slashCount: 3,
|
|
},
|
|
{
|
|
name: "att with different target, should not detect possible double",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{1, 2, 4, 6},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
BeaconBlockRoot: []byte("good block root"),
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{2, 4, 6},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 1,
|
|
Root: []byte("really good target"),
|
|
},
|
|
BeaconBlockRoot: []byte("really good block root"),
|
|
},
|
|
},
|
|
slashCount: 0,
|
|
},
|
|
{
|
|
name: "same att with different aggregates, should detect possible double",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{1, 2, 4, 6},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
BeaconBlockRoot: []byte("good block root"),
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{2, 3, 4, 16},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good target"),
|
|
},
|
|
BeaconBlockRoot: []byte("good block root"),
|
|
},
|
|
},
|
|
slashCount: 2,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
db := testDB.SetupSlasherDB(t, false)
|
|
ctx := context.Background()
|
|
|
|
sd := &SpanDetector{
|
|
slasherDB: db,
|
|
}
|
|
|
|
if err := sd.UpdateSpans(ctx, tt.att); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res, err := sd.DetectSlashingsForAttestation(ctx, tt.incomingAtt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var want []*types.DetectionResult
|
|
if tt.slashCount > 0 {
|
|
for _, indice := range sliceutil.IntersectionUint64(tt.att.AttestingIndices, tt.incomingAtt.AttestingIndices) {
|
|
want = append(want, &types.DetectionResult{
|
|
ValidatorIndex: indice,
|
|
Kind: types.DoubleVote,
|
|
SlashableEpoch: tt.incomingAtt.Data.Target.Epoch,
|
|
SigBytes: [2]byte{1, 2},
|
|
})
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(res, want) {
|
|
t.Errorf("Wanted: %v, received %v", want, res)
|
|
}
|
|
if uint64(len(res)) != tt.slashCount {
|
|
t.Fatalf("Unexpected amount of slashings found, received %d, expected %d", len(res), tt.slashCount)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanDetector_DetectSlashingsForAttestation_Surround(t *testing.T) {
|
|
type testStruct struct {
|
|
name string
|
|
sourceEpoch uint64
|
|
targetEpoch uint64
|
|
slashableEpoch uint64
|
|
shouldSlash bool
|
|
spansByEpochForValidator map[uint64][3]uint16
|
|
}
|
|
tests := []testStruct{
|
|
{
|
|
name: "Should slash if max span > distance",
|
|
sourceEpoch: 3,
|
|
targetEpoch: 6,
|
|
slashableEpoch: 7,
|
|
shouldSlash: true,
|
|
// Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to have
|
|
// committed a slashable offense by having a max span of 4 > distance.
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
3: {0, 4},
|
|
},
|
|
},
|
|
{
|
|
name: "Should NOT slash if max span < distance",
|
|
sourceEpoch: 3,
|
|
targetEpoch: 6,
|
|
// Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to NOT
|
|
// have committed slashable offense by having a max span of 1 < distance.
|
|
shouldSlash: false,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
3: {0, 1},
|
|
},
|
|
},
|
|
{
|
|
name: "Should NOT slash if max span == distance",
|
|
sourceEpoch: 3,
|
|
targetEpoch: 6,
|
|
// Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to NOT
|
|
// have committed slashable offense by having a max span of 3 == distance.
|
|
shouldSlash: false,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
3: {0, 3},
|
|
},
|
|
},
|
|
{
|
|
name: "Should NOT slash if min span == 0",
|
|
sourceEpoch: 3,
|
|
targetEpoch: 6,
|
|
// Given a min span of 0 and no max span slashing, we want validator to NOT
|
|
// have committed a slashable offense if min span == 0.
|
|
shouldSlash: false,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
3: {0, 1},
|
|
},
|
|
},
|
|
{
|
|
name: "Should slash if min span > 0 and min span < distance",
|
|
sourceEpoch: 3,
|
|
targetEpoch: 6,
|
|
// Given a distance of (6 - 3) = 3, we want the validator at epoch 3 to have
|
|
// committed a slashable offense by having a min span of 1 < distance.
|
|
shouldSlash: true,
|
|
slashableEpoch: 4,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
3: {1, 0},
|
|
},
|
|
},
|
|
// Proto Max Span Tests from the eth2-surround repo.
|
|
{
|
|
name: "Proto max span test #1",
|
|
sourceEpoch: 8,
|
|
targetEpoch: 18,
|
|
shouldSlash: false,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
0: {4, 0},
|
|
1: {2, 0},
|
|
2: {1, 0},
|
|
4: {0, 2},
|
|
5: {0, 1},
|
|
},
|
|
},
|
|
{
|
|
name: "Proto max span test #2",
|
|
sourceEpoch: 4,
|
|
targetEpoch: 12,
|
|
shouldSlash: false,
|
|
slashableEpoch: 0,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
4: {14, 2},
|
|
5: {13, 1},
|
|
6: {12, 0},
|
|
7: {11, 0},
|
|
9: {0, 9},
|
|
10: {0, 8},
|
|
11: {0, 7},
|
|
12: {0, 6},
|
|
13: {0, 5},
|
|
14: {0, 4},
|
|
15: {0, 3},
|
|
16: {0, 2},
|
|
17: {0, 1},
|
|
},
|
|
},
|
|
{
|
|
name: "Proto max span test #3",
|
|
sourceEpoch: 10,
|
|
targetEpoch: 15,
|
|
shouldSlash: true,
|
|
slashableEpoch: 18,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
4: {14, 2},
|
|
5: {13, 7},
|
|
6: {12, 6},
|
|
7: {11, 5},
|
|
8: {0, 4},
|
|
9: {0, 9},
|
|
10: {0, 8},
|
|
11: {0, 7},
|
|
12: {0, 6},
|
|
13: {0, 5},
|
|
14: {0, 4},
|
|
15: {0, 3},
|
|
16: {0, 2},
|
|
17: {0, 1},
|
|
},
|
|
},
|
|
// Proto Min Span Tests from the eth2-surround repo.
|
|
{
|
|
name: "Proto min span test #1",
|
|
sourceEpoch: 4,
|
|
targetEpoch: 6,
|
|
shouldSlash: false,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
1: {5, 0},
|
|
2: {4, 0},
|
|
3: {3, 0},
|
|
},
|
|
},
|
|
{
|
|
name: "Proto min span test #2",
|
|
sourceEpoch: 11,
|
|
targetEpoch: 15,
|
|
shouldSlash: false,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
1: {5, 0},
|
|
2: {4, 0},
|
|
3: {3, 0},
|
|
4: {14, 0},
|
|
5: {13, 1},
|
|
6: {12, 0},
|
|
7: {11, 0},
|
|
8: {10, 0},
|
|
9: {9, 0},
|
|
10: {8, 0},
|
|
11: {7, 0},
|
|
12: {6, 0},
|
|
14: {0, 4},
|
|
15: {0, 3},
|
|
16: {0, 2},
|
|
17: {0, 1},
|
|
},
|
|
},
|
|
{
|
|
name: "Proto min span test #3",
|
|
sourceEpoch: 9,
|
|
targetEpoch: 19,
|
|
shouldSlash: true,
|
|
slashableEpoch: 14,
|
|
spansByEpochForValidator: map[uint64][3]uint16{
|
|
0: {5, 0},
|
|
1: {4, 0},
|
|
2: {3, 0},
|
|
3: {11, 0},
|
|
4: {10, 1},
|
|
5: {9, 0},
|
|
6: {8, 0},
|
|
7: {7, 0},
|
|
8: {6, 0},
|
|
9: {5, 0},
|
|
10: {7, 0},
|
|
11: {6, 3},
|
|
12: {0, 2},
|
|
13: {0, 1},
|
|
14: {0, 3},
|
|
15: {0, 2},
|
|
16: {0, 1},
|
|
17: {0, 0},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
db := testDB.SetupSlasherDB(t, false)
|
|
ctx := context.Background()
|
|
|
|
sd := &SpanDetector{
|
|
slasherDB: db,
|
|
}
|
|
// We only care about validator index 0 for these tests for simplicity.
|
|
validatorIndex := uint64(0)
|
|
for k, v := range tt.spansByEpochForValidator {
|
|
span := map[uint64]types.Span{
|
|
validatorIndex: {
|
|
MinSpan: v[0],
|
|
MaxSpan: v[1],
|
|
},
|
|
}
|
|
if err := sd.slasherDB.SaveEpochSpansMap(ctx, k, span); err != nil {
|
|
t.Fatalf("Failed to save to slasherDB: %v", err)
|
|
}
|
|
}
|
|
|
|
att := ðpb.IndexedAttestation{
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: tt.sourceEpoch,
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: tt.targetEpoch,
|
|
},
|
|
},
|
|
AttestingIndices: []uint64{0},
|
|
}
|
|
res, err := sd.DetectSlashingsForAttestation(ctx, att)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !tt.shouldSlash && res != nil {
|
|
t.Fatalf("Did not want validator to be slashed but found slashable offense: %v", res)
|
|
}
|
|
if tt.shouldSlash {
|
|
want := []*types.DetectionResult{
|
|
{
|
|
Kind: types.SurroundVote,
|
|
SlashableEpoch: tt.slashableEpoch,
|
|
},
|
|
}
|
|
if !reflect.DeepEqual(res, want) {
|
|
t.Errorf("Wanted: %v, received %v", want, res)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanDetector_DetectSlashingsForAttestation_MultipleValidators(t *testing.T) {
|
|
type testStruct struct {
|
|
name string
|
|
incomingAtt *ethpb.IndexedAttestation
|
|
slashableEpochs []uint64
|
|
shouldSlash []bool
|
|
atts []*ethpb.IndexedAttestation
|
|
}
|
|
tests := []testStruct{
|
|
{
|
|
name: "3 of 4 validators slashed, differing histories",
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{0, 1, 2, 3},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 3,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 6,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
slashableEpochs: []uint64{6, 7, 5, 0},
|
|
// Detections - double, surround, surrounded, none.
|
|
shouldSlash: []bool{true, true, true, false},
|
|
// Atts in map: (src, epoch) - 0: (3, 6), 1: (2, 7), 2: (4, 5), 3: (5, 7)
|
|
atts: []*ethpb.IndexedAttestation{
|
|
indexedAttestation(3, 6, []uint64{0}),
|
|
indexedAttestation(2, 7, []uint64{1}),
|
|
indexedAttestation(4, 5, []uint64{2}),
|
|
indexedAttestation(5, 7, []uint64{3}),
|
|
},
|
|
},
|
|
{
|
|
name: "3 of 4 validators slashed, differing surrounds",
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{0, 1, 2, 3},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 5,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 7,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
slashableEpochs: []uint64{8, 9, 10, 0},
|
|
// Detections - surround, surround, surround, none.
|
|
shouldSlash: []bool{true, true, true, false},
|
|
// Atts in map: (src, epoch) - 0: (1, 8), 1: (3, 9), 2: (2, 10), 3: (4, 6)
|
|
atts: []*ethpb.IndexedAttestation{
|
|
indexedAttestation(1, 8, []uint64{0}),
|
|
indexedAttestation(3, 9, []uint64{1}),
|
|
indexedAttestation(2, 10, []uint64{2}),
|
|
indexedAttestation(4, 6, []uint64{3}),
|
|
},
|
|
},
|
|
{
|
|
name: "3 of 4 validators slashed, differing surrounded",
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{0, 1, 2, 3},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 9,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
slashableEpochs: []uint64{8, 8, 7, 0},
|
|
// Detections - surround, surround, surround, none.
|
|
shouldSlash: []bool{true, true, true, false},
|
|
// Atts in map: (src, epoch) - 0: (5, 8), 1: (3, 8), 2: (4, 7), 3: (1, 5)
|
|
atts: []*ethpb.IndexedAttestation{
|
|
indexedAttestation(5, 8, []uint64{0}),
|
|
indexedAttestation(3, 8, []uint64{1}),
|
|
indexedAttestation(4, 7, []uint64{2}),
|
|
indexedAttestation(1, 5, []uint64{3}),
|
|
},
|
|
},
|
|
{
|
|
name: "3 of 4 validators slashed, differing doubles",
|
|
incomingAtt: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{0, 1, 2, 3},
|
|
Data: ðpb.AttestationData{
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
Root: []byte("good source"),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 7,
|
|
Root: []byte("good target"),
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
slashableEpochs: []uint64{7, 7, 7, 0},
|
|
// Detections - surround, surround, surround, none.
|
|
shouldSlash: []bool{true, true, true, false},
|
|
// Atts in map: (src, epoch) - 0: (2, 7), 1: (3, 7), 2: (6, 7), 3: (1, 5)
|
|
atts: []*ethpb.IndexedAttestation{
|
|
indexedAttestation(2, 7, []uint64{0}),
|
|
indexedAttestation(3, 7, []uint64{1}),
|
|
indexedAttestation(6, 7, []uint64{2}),
|
|
indexedAttestation(1, 5, []uint64{3}),
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
db := testDB.SetupSlasherDB(t, false)
|
|
ctx := context.Background()
|
|
defer func() {
|
|
if err := db.ClearDB(); err != nil {
|
|
t.Log(err)
|
|
}
|
|
}()
|
|
defer func() {
|
|
if err := db.Close(); err != nil {
|
|
t.Log(err)
|
|
}
|
|
}()
|
|
|
|
spanDetector := &SpanDetector{
|
|
slasherDB: db,
|
|
}
|
|
for _, att := range tt.atts {
|
|
if err := spanDetector.UpdateSpans(ctx, att); err != nil {
|
|
t.Fatalf("Failed to save to slasherDB: %v", err)
|
|
}
|
|
}
|
|
res, err := spanDetector.DetectSlashingsForAttestation(ctx, tt.incomingAtt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var want []*types.DetectionResult
|
|
for i := 0; i < len(tt.incomingAtt.AttestingIndices); i++ {
|
|
if tt.shouldSlash[i] {
|
|
if tt.slashableEpochs[i] == tt.incomingAtt.Data.Target.Epoch {
|
|
want = append(want, &types.DetectionResult{
|
|
ValidatorIndex: uint64(i),
|
|
Kind: types.DoubleVote,
|
|
SlashableEpoch: tt.slashableEpochs[i],
|
|
SigBytes: [2]byte{1, 2},
|
|
})
|
|
} else {
|
|
want = append(want, &types.DetectionResult{
|
|
ValidatorIndex: uint64(i),
|
|
Kind: types.SurroundVote,
|
|
SlashableEpoch: tt.slashableEpochs[i],
|
|
SigBytes: [2]byte{1, 2},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(want, res) {
|
|
for i, ww := range want {
|
|
t.Errorf("Wanted %d: %+v\n", i, ww)
|
|
}
|
|
for i, rr := range res {
|
|
t.Errorf("Received %d: %+v\n", i, rr)
|
|
}
|
|
t.Errorf("Wanted: %v, received %v", want, res)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewSpanDetector_UpdateSpans(t *testing.T) {
|
|
type testStruct struct {
|
|
name string
|
|
att *ethpb.IndexedAttestation
|
|
want []map[uint64]types.Span
|
|
}
|
|
tests := []testStruct{
|
|
{
|
|
name: "Distance of 2 should update min spans accordingly",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{0, 1, 2},
|
|
Data: ðpb.AttestationData{
|
|
CommitteeIndex: 0,
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 2,
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 4,
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
want: []map[uint64]types.Span{
|
|
// Epoch 0.
|
|
{
|
|
0: {MinSpan: 4, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
1: {MinSpan: 4, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
2: {MinSpan: 4, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
},
|
|
// Epoch 1.
|
|
{
|
|
0: {MinSpan: 3, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
1: {MinSpan: 3, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
2: {MinSpan: 3, MaxSpan: 0, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
},
|
|
// Epoch 2.
|
|
{},
|
|
// Epoch 3.
|
|
{
|
|
0: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
1: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
2: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
},
|
|
// Epoch 4.
|
|
{
|
|
0: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true},
|
|
1: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true},
|
|
2: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true},
|
|
},
|
|
{},
|
|
{},
|
|
{},
|
|
},
|
|
},
|
|
{
|
|
name: "Distance of 4 should update max spans accordingly",
|
|
att: ðpb.IndexedAttestation{
|
|
AttestingIndices: []uint64{0, 1, 2},
|
|
Data: ðpb.AttestationData{
|
|
CommitteeIndex: 1,
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 5,
|
|
},
|
|
},
|
|
Signature: []byte{1, 2},
|
|
},
|
|
want: []map[uint64]types.Span{
|
|
// Epoch 0.
|
|
{},
|
|
// Epoch 1.
|
|
{
|
|
0: {MinSpan: 0, MaxSpan: 4, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
1: {MinSpan: 0, MaxSpan: 4, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
2: {MinSpan: 0, MaxSpan: 4, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
},
|
|
// Epoch 2.
|
|
{
|
|
0: {MinSpan: 0, MaxSpan: 3, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
1: {MinSpan: 0, MaxSpan: 3, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
2: {MinSpan: 0, MaxSpan: 3, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
},
|
|
// Epoch 3.
|
|
{
|
|
0: {MinSpan: 0, MaxSpan: 2, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
1: {MinSpan: 0, MaxSpan: 2, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
2: {MinSpan: 0, MaxSpan: 2, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
},
|
|
// Epoch 4.
|
|
{
|
|
0: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
1: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
2: {MinSpan: 0, MaxSpan: 1, SigBytes: [2]byte{0, 0}, HasAttested: false},
|
|
},
|
|
// Epoch 5.
|
|
{
|
|
0: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true},
|
|
1: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true},
|
|
2: {MinSpan: 0, MaxSpan: 0, SigBytes: [2]byte{1, 2}, HasAttested: true},
|
|
},
|
|
{},
|
|
{},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
db := testDB.SetupSlasherDB(t, false)
|
|
ctx := context.Background()
|
|
defer func() {
|
|
if err := db.ClearDB(); err != nil {
|
|
t.Log(err)
|
|
}
|
|
}()
|
|
defer func() {
|
|
if err := db.Close(); err != nil {
|
|
t.Log(err)
|
|
}
|
|
}()
|
|
|
|
sd := &SpanDetector{
|
|
slasherDB: db,
|
|
}
|
|
if err := sd.UpdateSpans(ctx, tt.att); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for epoch := range tt.want {
|
|
sm, _, err := sd.slasherDB.EpochSpansMap(ctx, uint64(epoch))
|
|
if err != nil {
|
|
t.Fatalf("Failed to read from slasherDB: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(sm, tt.want[epoch]) {
|
|
t.Errorf("Wanted and received:\n%v \n%v", tt.want, sm)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|