github.com/prysmaticlabs/prysm@v1.4.4/slasher/detection/detect_test.go (about)

     1  package detection
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
     8  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
     9  	slashpb "github.com/prysmaticlabs/prysm/proto/slashing"
    10  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    11  	"github.com/prysmaticlabs/prysm/shared/slashutil"
    12  	"github.com/prysmaticlabs/prysm/shared/sszutil"
    13  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    14  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    15  	testDB "github.com/prysmaticlabs/prysm/slasher/db/testing"
    16  	status "github.com/prysmaticlabs/prysm/slasher/db/types"
    17  	"github.com/prysmaticlabs/prysm/slasher/detection/attestations"
    18  	"github.com/prysmaticlabs/prysm/slasher/detection/attestations/types"
    19  	"github.com/prysmaticlabs/prysm/slasher/detection/proposals"
    20  	testDetect "github.com/prysmaticlabs/prysm/slasher/detection/testing"
    21  )
    22  
    23  func TestDetect_detectAttesterSlashings_Surround(t *testing.T) {
    24  	type testStruct struct {
    25  		name           string
    26  		savedAtts      []*ethpb.IndexedAttestation
    27  		incomingAtt    *ethpb.IndexedAttestation
    28  		slashingsFound int
    29  	}
    30  	tests := []testStruct{
    31  		{
    32  			name: "surrounding vote detected should report a slashing",
    33  			savedAtts: []*ethpb.IndexedAttestation{
    34  				{
    35  					AttestingIndices: []uint64{3},
    36  					Data: &ethpb.AttestationData{
    37  						Source:          &ethpb.Checkpoint{Epoch: 9, Root: make([]byte, 32)},
    38  						Target:          &ethpb.Checkpoint{Epoch: 13, Root: make([]byte, 32)},
    39  						BeaconBlockRoot: make([]byte, 32),
    40  					},
    41  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
    42  				},
    43  			},
    44  			incomingAtt: &ethpb.IndexedAttestation{
    45  				AttestingIndices: []uint64{1, 3, 7},
    46  				Data: &ethpb.AttestationData{
    47  					Source:          &ethpb.Checkpoint{Epoch: 7, Root: make([]byte, 32)},
    48  					Target:          &ethpb.Checkpoint{Epoch: 14, Root: make([]byte, 32)},
    49  					BeaconBlockRoot: make([]byte, 32),
    50  				},
    51  				Signature: make([]byte, 96),
    52  			},
    53  			slashingsFound: 1,
    54  		},
    55  		{
    56  			name: "surrounded vote detected should report a slashing",
    57  			savedAtts: []*ethpb.IndexedAttestation{
    58  				{
    59  					AttestingIndices: []uint64{0, 2, 4, 8},
    60  					Data: &ethpb.AttestationData{
    61  						Source:          &ethpb.Checkpoint{Epoch: 6, Root: make([]byte, 32)},
    62  						Target:          &ethpb.Checkpoint{Epoch: 10, Root: make([]byte, 32)},
    63  						BeaconBlockRoot: make([]byte, 32),
    64  					},
    65  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
    66  				},
    67  			},
    68  			incomingAtt: &ethpb.IndexedAttestation{
    69  				AttestingIndices: []uint64{0, 4},
    70  				Data: &ethpb.AttestationData{
    71  					Source:          &ethpb.Checkpoint{Epoch: 7, Root: make([]byte, 32)},
    72  					Target:          &ethpb.Checkpoint{Epoch: 9, Root: make([]byte, 32)},
    73  					BeaconBlockRoot: make([]byte, 32),
    74  				},
    75  				Signature: make([]byte, 96),
    76  			},
    77  			slashingsFound: 1,
    78  		},
    79  		{
    80  			name: "2 different surrounded votes detected should report 2 slashings",
    81  			savedAtts: []*ethpb.IndexedAttestation{
    82  				{
    83  					AttestingIndices: []uint64{0, 2},
    84  					Data: &ethpb.AttestationData{
    85  						Source:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
    86  						Target:          &ethpb.Checkpoint{Epoch: 5, Root: make([]byte, 32)},
    87  						BeaconBlockRoot: make([]byte, 32),
    88  					},
    89  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
    90  				},
    91  				{
    92  					AttestingIndices: []uint64{4, 8},
    93  					Data: &ethpb.AttestationData{
    94  						Source:          &ethpb.Checkpoint{Epoch: 3, Root: make([]byte, 32)},
    95  						Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
    96  						BeaconBlockRoot: make([]byte, 32),
    97  					},
    98  					Signature: bytesutil.PadTo([]byte{1, 3}, 96),
    99  				},
   100  			},
   101  			incomingAtt: &ethpb.IndexedAttestation{
   102  				AttestingIndices: []uint64{0, 4},
   103  				Data: &ethpb.AttestationData{
   104  					Source:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   105  					Target:          &ethpb.Checkpoint{Epoch: 7, Root: make([]byte, 32)},
   106  					BeaconBlockRoot: make([]byte, 32),
   107  				},
   108  				Signature: make([]byte, 96),
   109  			},
   110  			slashingsFound: 2,
   111  		},
   112  		{
   113  			name: "2 different surrounding votes detected should report 2 slashings",
   114  			savedAtts: []*ethpb.IndexedAttestation{
   115  				{
   116  					AttestingIndices: []uint64{0, 2},
   117  					Data: &ethpb.AttestationData{
   118  						Source:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   119  						Target:          &ethpb.Checkpoint{Epoch: 10, Root: make([]byte, 32)},
   120  						BeaconBlockRoot: make([]byte, 32),
   121  					},
   122  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   123  				},
   124  				{
   125  					AttestingIndices: []uint64{4, 8},
   126  					Data: &ethpb.AttestationData{
   127  						Source:          &ethpb.Checkpoint{Epoch: 5, Root: make([]byte, 32)},
   128  						Target:          &ethpb.Checkpoint{Epoch: 9, Root: make([]byte, 32)},
   129  						BeaconBlockRoot: make([]byte, 32),
   130  					},
   131  					Signature: bytesutil.PadTo([]byte{1, 3}, 96),
   132  				},
   133  			},
   134  			incomingAtt: &ethpb.IndexedAttestation{
   135  				AttestingIndices: []uint64{0, 4},
   136  				Data: &ethpb.AttestationData{
   137  					Source:          &ethpb.Checkpoint{Epoch: 7, Root: make([]byte, 32)},
   138  					Target:          &ethpb.Checkpoint{Epoch: 8, Root: make([]byte, 32)},
   139  					BeaconBlockRoot: make([]byte, 32),
   140  				},
   141  				Signature: make([]byte, 96),
   142  			},
   143  			slashingsFound: 2,
   144  		},
   145  		{
   146  			name: "no slashable detected should not report a slashing",
   147  			savedAtts: []*ethpb.IndexedAttestation{
   148  				{
   149  					AttestingIndices: []uint64{0},
   150  					Data: &ethpb.AttestationData{
   151  						Source:          &ethpb.Checkpoint{Epoch: 1, Root: make([]byte, 32)},
   152  						Target:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   153  						BeaconBlockRoot: make([]byte, 32),
   154  					},
   155  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   156  				},
   157  			},
   158  			incomingAtt: &ethpb.IndexedAttestation{
   159  				AttestingIndices: []uint64{0},
   160  				Data: &ethpb.AttestationData{
   161  					Source:          &ethpb.Checkpoint{Epoch: 0, Root: make([]byte, 32)},
   162  					Target:          &ethpb.Checkpoint{Epoch: 1, Root: make([]byte, 32)},
   163  					BeaconBlockRoot: make([]byte, 32),
   164  				},
   165  				Signature: make([]byte, 96),
   166  			},
   167  			slashingsFound: 0,
   168  		},
   169  	}
   170  	for _, tt := range tests {
   171  		t.Run(tt.name, func(t *testing.T) {
   172  			db := testDB.SetupSlasherDB(t, false)
   173  			ctx := context.Background()
   174  			ds := Service{
   175  				ctx:                ctx,
   176  				cfg:                &Config{SlasherDB: db},
   177  				minMaxSpanDetector: attestations.NewSpanDetector(db),
   178  			}
   179  			require.NoError(t, db.SaveIndexedAttestations(ctx, tt.savedAtts))
   180  			for _, att := range tt.savedAtts {
   181  				require.NoError(t, ds.minMaxSpanDetector.UpdateSpans(ctx, att))
   182  			}
   183  
   184  			slashings, err := ds.DetectAttesterSlashings(ctx, tt.incomingAtt)
   185  			require.NoError(t, err)
   186  			require.Equal(t, tt.slashingsFound, len(slashings), "Unexpected amount of slashings found")
   187  			attsl, err := db.AttesterSlashings(ctx, status.Active)
   188  			require.NoError(t, err)
   189  			require.Equal(t, tt.slashingsFound, len(attsl), "Didnt save slashing to db")
   190  			for _, ss := range slashings {
   191  				slashingAtt1 := ss.Attestation_1
   192  				slashingAtt2 := ss.Attestation_2
   193  				if !slashutil.IsSurround(slashingAtt1, slashingAtt2) {
   194  					t.Fatalf(
   195  						"Expected slashing to be valid, received atts %d->%d and %d->%d",
   196  						slashingAtt2.Data.Source.Epoch,
   197  						slashingAtt2.Data.Target.Epoch,
   198  						slashingAtt1.Data.Source.Epoch,
   199  						slashingAtt1.Data.Target.Epoch,
   200  					)
   201  				}
   202  			}
   203  		})
   204  	}
   205  }
   206  
   207  func TestDetect_detectAttesterSlashings_Double(t *testing.T) {
   208  	type testStruct struct {
   209  		name           string
   210  		savedAtts      []*ethpb.IndexedAttestation
   211  		incomingAtt    *ethpb.IndexedAttestation
   212  		slashingsFound int
   213  	}
   214  	tests := []testStruct{
   215  		{
   216  			name: "different source, same target, should report a slashing",
   217  			savedAtts: []*ethpb.IndexedAttestation{
   218  				{
   219  					AttestingIndices: []uint64{3},
   220  					Data: &ethpb.AttestationData{
   221  						Source:          &ethpb.Checkpoint{Epoch: 3, Root: make([]byte, 32)},
   222  						Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   223  						BeaconBlockRoot: make([]byte, 32),
   224  					},
   225  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   226  				},
   227  			},
   228  			incomingAtt: &ethpb.IndexedAttestation{
   229  				AttestingIndices: []uint64{1, 3, 7},
   230  				Data: &ethpb.AttestationData{
   231  					Source:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   232  					Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   233  					BeaconBlockRoot: make([]byte, 32),
   234  				},
   235  				Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   236  			},
   237  			slashingsFound: 1,
   238  		},
   239  		{
   240  			name: "different histories, same target, should report 2 slashings",
   241  			savedAtts: []*ethpb.IndexedAttestation{
   242  				{
   243  					AttestingIndices: []uint64{1},
   244  					Data: &ethpb.AttestationData{
   245  						Source:          &ethpb.Checkpoint{Epoch: 3, Root: make([]byte, 32)},
   246  						Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   247  						BeaconBlockRoot: make([]byte, 32),
   248  					},
   249  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   250  				},
   251  				{
   252  					AttestingIndices: []uint64{3},
   253  					Data: &ethpb.AttestationData{
   254  						Source:          &ethpb.Checkpoint{Epoch: 1, Root: make([]byte, 32)},
   255  						Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   256  						BeaconBlockRoot: make([]byte, 32),
   257  					},
   258  					Signature: bytesutil.PadTo([]byte{1, 3}, 96),
   259  				},
   260  			},
   261  			incomingAtt: &ethpb.IndexedAttestation{
   262  				AttestingIndices: []uint64{1, 3, 7},
   263  				Data: &ethpb.AttestationData{
   264  					Source:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   265  					Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   266  					BeaconBlockRoot: make([]byte, 32),
   267  				},
   268  				Signature: bytesutil.PadTo([]byte{1, 4}, 96),
   269  			},
   270  			slashingsFound: 2,
   271  		},
   272  		{
   273  			name: "same source and target, different block root, should report a slashing ",
   274  			savedAtts: []*ethpb.IndexedAttestation{
   275  				{
   276  					AttestingIndices: []uint64{0, 2, 4, 8},
   277  					Data: &ethpb.AttestationData{
   278  						Source:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   279  						Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   280  						BeaconBlockRoot: bytesutil.PadTo([]byte("good block root"), 32),
   281  					},
   282  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   283  				},
   284  			},
   285  			incomingAtt: &ethpb.IndexedAttestation{
   286  				AttestingIndices: []uint64{0, 4},
   287  				Data: &ethpb.AttestationData{
   288  					Source:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   289  					Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   290  					BeaconBlockRoot: bytesutil.PadTo([]byte("bad block root"), 32),
   291  				},
   292  				Signature: make([]byte, 96),
   293  			},
   294  			slashingsFound: 1,
   295  		},
   296  		{
   297  			name: "same attestation should not report double",
   298  			savedAtts: []*ethpb.IndexedAttestation{
   299  				{
   300  					AttestingIndices: []uint64{0},
   301  					Data: &ethpb.AttestationData{
   302  						Source:          &ethpb.Checkpoint{Epoch: 0, Root: make([]byte, 32)},
   303  						Target:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   304  						BeaconBlockRoot: bytesutil.PadTo([]byte("good block root"), 32),
   305  					},
   306  					Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   307  				},
   308  			},
   309  			incomingAtt: &ethpb.IndexedAttestation{
   310  				AttestingIndices: []uint64{0},
   311  				Data: &ethpb.AttestationData{
   312  					Source:          &ethpb.Checkpoint{Epoch: 0, Root: make([]byte, 32)},
   313  					Target:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   314  					BeaconBlockRoot: bytesutil.PadTo([]byte("good block root"), 32),
   315  				},
   316  				Signature: make([]byte, 96),
   317  			},
   318  			slashingsFound: 0,
   319  		},
   320  	}
   321  	for _, tt := range tests {
   322  		t.Run(tt.name, func(t *testing.T) {
   323  			db := testDB.SetupSlasherDB(t, false)
   324  			ctx := context.Background()
   325  			ds := Service{
   326  				ctx:                ctx,
   327  				cfg:                &Config{SlasherDB: db},
   328  				minMaxSpanDetector: attestations.NewSpanDetector(db),
   329  			}
   330  			require.NoError(t, db.SaveIndexedAttestations(ctx, tt.savedAtts))
   331  			for _, att := range tt.savedAtts {
   332  				require.NoError(t, ds.minMaxSpanDetector.UpdateSpans(ctx, att))
   333  			}
   334  
   335  			slashings, err := ds.DetectAttesterSlashings(ctx, tt.incomingAtt)
   336  			require.NoError(t, err)
   337  			require.Equal(t, tt.slashingsFound, len(slashings), "Unexpected amount of slashings found")
   338  			savedSlashings, err := db.AttesterSlashings(ctx, status.Active)
   339  			require.NoError(t, err)
   340  			require.Equal(t, tt.slashingsFound, len(savedSlashings), "Did not save slashing to db")
   341  
   342  			for _, ss := range slashings {
   343  				slashingAtt1 := ss.Attestation_1
   344  				slashingAtt2 := ss.Attestation_2
   345  				if !isDoubleVote(slashingAtt1, slashingAtt2) {
   346  					t.Fatalf(
   347  						"Expected slashing to be valid, received atts with target epoch %d and %d but not valid",
   348  						slashingAtt2.Data.Target.Epoch,
   349  						slashingAtt1.Data.Target.Epoch,
   350  					)
   351  				}
   352  			}
   353  		})
   354  	}
   355  }
   356  
   357  func TestDetect_updateHighestAttestation(t *testing.T) {
   358  	tests := []struct {
   359  		name         string
   360  		savedHighest *slashpb.HighestAttestation
   361  		incomingAtt  *ethpb.IndexedAttestation
   362  		expected     *slashpb.HighestAttestation
   363  	}{
   364  		{
   365  			name: "update only target to higher",
   366  			savedHighest: &slashpb.HighestAttestation{
   367  				ValidatorId:        1,
   368  				HighestSourceEpoch: 1,
   369  				HighestTargetEpoch: 2,
   370  			},
   371  			incomingAtt: &ethpb.IndexedAttestation{
   372  				AttestingIndices: []uint64{1, 3, 7},
   373  				Data: &ethpb.AttestationData{
   374  					Source:          &ethpb.Checkpoint{Epoch: 1, Root: make([]byte, 32)},
   375  					Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   376  					BeaconBlockRoot: make([]byte, 32),
   377  				},
   378  				Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   379  			},
   380  			expected: &slashpb.HighestAttestation{
   381  				ValidatorId:        1,
   382  				HighestSourceEpoch: 1,
   383  				HighestTargetEpoch: 4,
   384  			},
   385  		},
   386  		{
   387  			name: "update target and source to higher",
   388  			savedHighest: &slashpb.HighestAttestation{
   389  				ValidatorId:        1,
   390  				HighestSourceEpoch: 1,
   391  				HighestTargetEpoch: 2,
   392  			},
   393  			incomingAtt: &ethpb.IndexedAttestation{
   394  				AttestingIndices: []uint64{1, 3, 7},
   395  				Data: &ethpb.AttestationData{
   396  					Source:          &ethpb.Checkpoint{Epoch: 3, Root: make([]byte, 32)},
   397  					Target:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   398  					BeaconBlockRoot: make([]byte, 32),
   399  				},
   400  				Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   401  			},
   402  			expected: &slashpb.HighestAttestation{
   403  				ValidatorId:        1,
   404  				HighestSourceEpoch: 3,
   405  				HighestTargetEpoch: 4,
   406  			},
   407  		},
   408  		{
   409  			name: "no update",
   410  			savedHighest: &slashpb.HighestAttestation{
   411  				ValidatorId:        1,
   412  				HighestSourceEpoch: 1,
   413  				HighestTargetEpoch: 2,
   414  			},
   415  			incomingAtt: &ethpb.IndexedAttestation{
   416  				AttestingIndices: []uint64{1, 3, 7},
   417  				Data: &ethpb.AttestationData{
   418  					Source:          &ethpb.Checkpoint{Epoch: 1, Root: make([]byte, 32)},
   419  					Target:          &ethpb.Checkpoint{Epoch: 2, Root: make([]byte, 32)},
   420  					BeaconBlockRoot: make([]byte, 32),
   421  				},
   422  				Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   423  			},
   424  			expected: &slashpb.HighestAttestation{
   425  				ValidatorId:        1,
   426  				HighestSourceEpoch: 1,
   427  				HighestTargetEpoch: 2,
   428  			},
   429  		},
   430  		{
   431  			name: "update target to higher when source is lower(should be a slashable attestation)",
   432  			savedHighest: &slashpb.HighestAttestation{
   433  				ValidatorId:        1,
   434  				HighestSourceEpoch: 5,
   435  				HighestTargetEpoch: 6,
   436  			},
   437  			incomingAtt: &ethpb.IndexedAttestation{
   438  				AttestingIndices: []uint64{1, 3, 7},
   439  				Data: &ethpb.AttestationData{
   440  					Source:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   441  					Target:          &ethpb.Checkpoint{Epoch: 8, Root: make([]byte, 32)},
   442  					BeaconBlockRoot: make([]byte, 32),
   443  				},
   444  				Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   445  			},
   446  			expected: &slashpb.HighestAttestation{
   447  				ValidatorId:        1,
   448  				HighestSourceEpoch: 5,
   449  				HighestTargetEpoch: 8,
   450  			},
   451  		},
   452  		{
   453  			name: "update source to higher when target is same",
   454  			savedHighest: &slashpb.HighestAttestation{
   455  				ValidatorId:        1,
   456  				HighestSourceEpoch: 3,
   457  				HighestTargetEpoch: 6,
   458  			},
   459  			incomingAtt: &ethpb.IndexedAttestation{
   460  				AttestingIndices: []uint64{1, 3, 7},
   461  				Data: &ethpb.AttestationData{
   462  					Source:          &ethpb.Checkpoint{Epoch: 4, Root: make([]byte, 32)},
   463  					Target:          &ethpb.Checkpoint{Epoch: 6, Root: make([]byte, 32)},
   464  					BeaconBlockRoot: make([]byte, 32),
   465  				},
   466  				Signature: bytesutil.PadTo([]byte{1, 2}, 96),
   467  			},
   468  			expected: &slashpb.HighestAttestation{
   469  				ValidatorId:        1,
   470  				HighestSourceEpoch: 4,
   471  				HighestTargetEpoch: 6,
   472  			},
   473  		},
   474  	}
   475  
   476  	for _, tt := range tests {
   477  		t.Run(tt.name, func(t *testing.T) {
   478  			db := testDB.SetupSlasherDB(t, false)
   479  			ctx := context.Background()
   480  			ds := Service{
   481  				ctx:               ctx,
   482  				cfg:               &Config{SlasherDB: db},
   483  				proposalsDetector: proposals.NewProposeDetector(db),
   484  			}
   485  			require.NoError(t, db.SaveHighestAttestation(ctx, tt.savedHighest))
   486  
   487  			// Update and assert.
   488  			require.NoError(t, ds.UpdateHighestAttestation(ctx, tt.incomingAtt))
   489  			h, err := db.HighestAttestation(ctx, tt.savedHighest.ValidatorId)
   490  			require.NoError(t, err)
   491  			assert.Equal(t, tt.expected.HighestSourceEpoch, h.HighestSourceEpoch)
   492  			assert.Equal(t, tt.expected.HighestTargetEpoch, h.HighestTargetEpoch)
   493  		})
   494  	}
   495  }
   496  
   497  func TestDetect_detectProposerSlashing(t *testing.T) {
   498  	type testStruct struct {
   499  		name        string
   500  		blk         *ethpb.SignedBeaconBlockHeader
   501  		incomingBlk *ethpb.SignedBeaconBlockHeader
   502  		slashing    *ethpb.ProposerSlashing
   503  	}
   504  	s0, err := helpers.StartSlot(0)
   505  	require.NoError(t, err)
   506  	sigBlk1slot0, err := testDetect.SignedBlockHeader(s0, 0)
   507  	require.NoError(t, err)
   508  	sigBlk2slot0, err := testDetect.SignedBlockHeader(s0, 0)
   509  	require.NoError(t, err)
   510  	s1, err := helpers.StartSlot(1)
   511  	require.NoError(t, err)
   512  	sigBlk1epoch1, err := testDetect.SignedBlockHeader(s1, 0)
   513  	require.NoError(t, err)
   514  	tests := []testStruct{
   515  		{
   516  			name:        "same block sig dont slash",
   517  			blk:         sigBlk1slot0,
   518  			incomingBlk: sigBlk1slot0,
   519  			slashing:    nil,
   520  		},
   521  		{
   522  			name:        "block from different epoch dont slash",
   523  			blk:         sigBlk1slot0,
   524  			incomingBlk: sigBlk1epoch1,
   525  			slashing:    nil,
   526  		},
   527  		{
   528  			name:        "different sig from same slot slash",
   529  			blk:         sigBlk1slot0,
   530  			incomingBlk: sigBlk2slot0,
   531  			slashing:    &ethpb.ProposerSlashing{Header_1: sigBlk2slot0, Header_2: sigBlk1slot0},
   532  		},
   533  	}
   534  	for _, tt := range tests {
   535  		t.Run(tt.name, func(t *testing.T) {
   536  			db := testDB.SetupSlasherDB(t, false)
   537  			ctx := context.Background()
   538  			ds := Service{
   539  				ctx:               ctx,
   540  				cfg:               &Config{SlasherDB: db},
   541  				proposalsDetector: proposals.NewProposeDetector(db),
   542  			}
   543  			require.NoError(t, db.SaveBlockHeader(ctx, tt.blk))
   544  
   545  			slashing, err := ds.proposalsDetector.DetectDoublePropose(ctx, tt.incomingBlk)
   546  			require.NoError(t, err)
   547  			assert.DeepEqual(t, tt.slashing, slashing)
   548  			savedSlashings, err := db.ProposalSlashingsByStatus(ctx, status.Active)
   549  			require.NoError(t, err)
   550  			if tt.slashing != nil {
   551  				require.Equal(t, 1, len(savedSlashings), "Did not save slashing to db")
   552  			}
   553  
   554  			if slashing != nil && !isDoublePropose(slashing.Header_1, slashing.Header_2) {
   555  				t.Fatalf(
   556  					"Expected slashing to be valid, received atts with target epoch %v and %v but not valid",
   557  					slashing.Header_1,
   558  					slashing.Header_2,
   559  				)
   560  			}
   561  		})
   562  	}
   563  }
   564  func TestDetect_detectProposerSlashingNoUpdate(t *testing.T) {
   565  	type testStruct struct {
   566  		name        string
   567  		blk         *ethpb.SignedBeaconBlockHeader
   568  		noUpdtaeBlk *ethpb.BeaconBlockHeader
   569  		slashable   bool
   570  	}
   571  	s0, err := helpers.StartSlot(0)
   572  	require.NoError(t, err)
   573  	sigBlk1slot0, err := testDetect.SignedBlockHeader(s0, 0)
   574  	require.NoError(t, err)
   575  	blk1slot0, err := testDetect.BlockHeader(s0, 0)
   576  	require.NoError(t, err)
   577  	blk2slot0, err := testDetect.BlockHeader(s0, 0)
   578  	require.NoError(t, err)
   579  	diffRoot := [32]byte{1, 1, 1}
   580  	blk2slot0.ParentRoot = diffRoot[:]
   581  	blk3slot0, err := testDetect.BlockHeader(s0, 0)
   582  	require.NoError(t, err)
   583  	blk3slot0.StateRoot = diffRoot[:]
   584  	blk4slot0, err := testDetect.BlockHeader(s0, 0)
   585  	require.NoError(t, err)
   586  	blk4slot0.BodyRoot = diffRoot[:]
   587  	tests := []testStruct{
   588  		{
   589  			name:        "same block don't slash",
   590  			blk:         sigBlk1slot0,
   591  			noUpdtaeBlk: blk1slot0,
   592  			slashable:   false,
   593  		},
   594  		{
   595  			name:        "diff parent root slash",
   596  			blk:         sigBlk1slot0,
   597  			noUpdtaeBlk: blk2slot0,
   598  			slashable:   true,
   599  		},
   600  		{
   601  			name:        "diff state root slash",
   602  			blk:         sigBlk1slot0,
   603  			noUpdtaeBlk: blk3slot0,
   604  			slashable:   true,
   605  		},
   606  		{
   607  			name:        "diff body root slash",
   608  			blk:         sigBlk1slot0,
   609  			noUpdtaeBlk: blk4slot0,
   610  			slashable:   true,
   611  		},
   612  	}
   613  	for _, tt := range tests {
   614  		t.Run(tt.name, func(t *testing.T) {
   615  			db := testDB.SetupSlasherDB(t, false)
   616  			ctx := context.Background()
   617  			ds := Service{
   618  				ctx:               ctx,
   619  				cfg:               &Config{SlasherDB: db},
   620  				proposalsDetector: proposals.NewProposeDetector(db),
   621  			}
   622  			require.NoError(t, db.SaveBlockHeader(ctx, tt.blk))
   623  
   624  			slashble, err := ds.proposalsDetector.DetectDoubleProposeNoUpdate(ctx, tt.noUpdtaeBlk)
   625  			require.NoError(t, err)
   626  			assert.Equal(t, tt.slashable, slashble)
   627  		})
   628  	}
   629  }
   630  
   631  func TestServer_MapResultsToAtts(t *testing.T) {
   632  	db := testDB.SetupSlasherDB(t, false)
   633  	ctx := context.Background()
   634  	ds := Service{
   635  		ctx: ctx,
   636  		cfg: &Config{SlasherDB: db},
   637  	}
   638  	// 3 unique results, but 7 validators in total.
   639  	results := []*types.DetectionResult{
   640  		// 3 For the same slashable epoch and same sigs.
   641  		{
   642  			ValidatorIndex: 1,
   643  			SlashableEpoch: 5,
   644  			Kind:           types.DoubleVote,
   645  			SigBytes:       [2]byte{5, 5},
   646  		},
   647  		{
   648  			ValidatorIndex: 2,
   649  			SlashableEpoch: 5,
   650  			Kind:           types.DoubleVote,
   651  			SigBytes:       [2]byte{5, 5},
   652  		},
   653  		{
   654  			ValidatorIndex: 3,
   655  			SlashableEpoch: 5,
   656  			Kind:           types.DoubleVote,
   657  			SigBytes:       [2]byte{5, 5},
   658  		},
   659  		// Different signature.
   660  		{
   661  			ValidatorIndex: 5,
   662  			SlashableEpoch: 5,
   663  			Kind:           types.DoubleVote,
   664  			SigBytes:       [2]byte{3, 5},
   665  		},
   666  		// Different slashable epoch.
   667  		{
   668  			ValidatorIndex: 5,
   669  			SlashableEpoch: 4,
   670  			Kind:           types.DoubleVote,
   671  			SigBytes:       [2]byte{5, 5},
   672  		},
   673  		// Different both.
   674  		{
   675  			ValidatorIndex: 8,
   676  			SlashableEpoch: 6,
   677  			Kind:           types.DoubleVote,
   678  			SigBytes:       [2]byte{2, 1},
   679  		},
   680  		{
   681  			ValidatorIndex: 7,
   682  			SlashableEpoch: 6,
   683  			Kind:           types.DoubleVote,
   684  			SigBytes:       [2]byte{2, 1},
   685  		},
   686  	}
   687  	expectedResultsToAtts := map[[32]byte][]*ethpb.IndexedAttestation{
   688  		resultHash(results[0]): {
   689  			createIndexedAttForResult(results[0]),
   690  			createIndexedAttForResult(results[1]),
   691  			createIndexedAttForResult(results[2]),
   692  		},
   693  		resultHash(results[3]): {
   694  			createIndexedAttForResult(results[3]),
   695  		},
   696  		resultHash(results[4]): {
   697  			createIndexedAttForResult(results[4]),
   698  		},
   699  		resultHash(results[5]): {
   700  			createIndexedAttForResult(results[6]),
   701  			createIndexedAttForResult(results[5]),
   702  		},
   703  	}
   704  	for _, atts := range expectedResultsToAtts {
   705  		require.NoError(t, ds.cfg.SlasherDB.SaveIndexedAttestations(ctx, atts))
   706  	}
   707  
   708  	resultsToAtts, err := ds.mapResultsToAtts(ctx, results)
   709  	require.NoError(t, err)
   710  	for k := range expectedResultsToAtts {
   711  		exp := expectedResultsToAtts[k]
   712  		recv := resultsToAtts[k]
   713  		if !sszutil.DeepEqual(exp, recv) {
   714  			t.Error("Expected map:")
   715  			for key, value := range resultsToAtts {
   716  				t.Errorf("Key %#x: %d atts", key, len(value))
   717  				t.Errorf("%+v", value)
   718  			}
   719  			t.Error("To equal:")
   720  			for key, value := range expectedResultsToAtts {
   721  				t.Errorf("Key %#x: %d atts", key, len(value))
   722  				t.Errorf("%+v", value)
   723  			}
   724  		}
   725  	}
   726  }
   727  
   728  func createIndexedAttForResult(result *types.DetectionResult) *ethpb.IndexedAttestation {
   729  	return &ethpb.IndexedAttestation{
   730  		AttestingIndices: []uint64{result.ValidatorIndex},
   731  		Data: &ethpb.AttestationData{
   732  			BeaconBlockRoot: []byte("text block root"),
   733  			Target: &ethpb.Checkpoint{
   734  				Epoch: result.SlashableEpoch,
   735  			},
   736  		},
   737  		Signature: append(result.SigBytes[:], []byte{uint8(result.ValidatorIndex), 4, 5, 6, 7, 8}...),
   738  	}
   739  }