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: &ethpb.SignedBeaconBlockHeader{
   224  					Header: &ethpb.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: &ethpb.IndexedAttestation{
   276  					Data: &ethpb.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 := &ethpb.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: &ethpb.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 := &ethpb.AttestationData{
   472  		BeaconBlockRoot: params.BeaconConfig().ZeroHash[:],
   473  		Source: &ethpb.Checkpoint{
   474  			Epoch: source,
   475  			Root:  params.BeaconConfig().ZeroHash[:],
   476  		},
   477  		Target: &ethpb.Checkpoint{
   478  			Epoch: target,
   479  			Root:  params.BeaconConfig().ZeroHash[:],
   480  		},
   481  	}
   482  	return &slashertypes.IndexedAttestationWrapper{
   483  		IndexedAttestation: &ethpb.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  }