Files
prysm/beacon-chain/slasher/helpers_test.go
Radosław Kapka 2c5a2e8ec7 Electra attestation interfaces (#13937)
* config values

* block protos

* get_committee_indices

* proto and ssz

* attestation interface

* Revert "Auxiliary commit to revert individual files from deadb2183723511721b3288c7168808a4fa97c64"

This reverts commit 32ad5009537bc5ec0e6caf9f52143d380d00be85.

* todos

* get_attesting_indices

* Revert "Auxiliary commit to revert individual files from dd2789723f90b15eb1f874b561d88d11dcc9c0bf"

This reverts commit f39644ed3cb6f3964fc6c86fdf4bd5de2a9668c8.

* beacon spec changes

* Fix pending attestation. Build ok

* Electra: add electra version

* Electra: consensus types

* gocognit exclusion

* @potuz's suggestion

* build fix

* interfaces for indexed att and slashing

* indexed att usage

* BuildSignedBeaconBlockFromExecutionPayload

* slashing usage

* grpc stubs

* remove unused methods

* Electra attestation interfaces

* cleanup

* tests

* make linter happy

* simple casting

* test fixes

* Fix spectest failures

* Regen pb and ssz files

* Handle "not ok" type assertion cases

* Setters that check version should always return an error. SetAttesterSlashings and SetAttestations

* gofmt

* Fix TestMinSpanChunksSlice_CheckSlashable

---------

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: Preston Van Loon <preston@pvl.dev>
2024-04-30 21:29:38 +00:00

518 lines
13 KiB
Go

package slasher
import (
"reflect"
"testing"
slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
logTest "github.com/sirupsen/logrus/hooks/test"
)
func TestService_groupByValidatorChunkIndex(t *testing.T) {
tests := []struct {
name string
params *Parameters
atts []*slashertypes.IndexedAttestationWrapper
want map[uint64][]*slashertypes.IndexedAttestationWrapper
}{
{
name: "No attestations returns empty map",
params: DefaultParams(),
atts: make([]*slashertypes.IndexedAttestationWrapper, 0),
want: make(map[uint64][]*slashertypes.IndexedAttestationWrapper),
},
{
name: "Groups multiple attestations belonging to single validator chunk",
params: &Parameters{
validatorChunkSize: 2,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
},
},
},
{
name: "Groups single attestation belonging to multiple validator chunk",
params: &Parameters{
validatorChunkSize: 2,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
},
1: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
},
2: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
params: tt.params,
}
if got := s.groupByValidatorChunkIndex(tt.atts); !reflect.DeepEqual(got, tt.want) {
t.Errorf("groupByValidatorChunkIndex() = %v, want %v", got, tt.want)
}
})
}
}
func TestService_groupByChunkIndex(t *testing.T) {
tests := []struct {
name string
params *Parameters
atts []*slashertypes.IndexedAttestationWrapper
want map[uint64][]*slashertypes.IndexedAttestationWrapper
}{
{
name: "No attestations returns empty map",
params: DefaultParams(),
atts: make([]*slashertypes.IndexedAttestationWrapper, 0),
want: make(map[uint64][]*slashertypes.IndexedAttestationWrapper),
},
{
name: "Groups multiple attestations belonging to single chunk",
params: &Parameters{
chunkSize: 2,
historyLength: 3,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
},
},
},
{
name: "Groups multiple attestations belonging to multiple chunks",
params: &Parameters{
chunkSize: 2,
historyLength: 3,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
createAttestationWrapperEmptySig(t, 2, 0, nil, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
},
1: {
createAttestationWrapperEmptySig(t, 2, 0, nil, nil),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
params: tt.params,
}
if got := s.groupByChunkIndex(tt.atts); !reflect.DeepEqual(got, tt.want) {
t.Errorf("groupByChunkIndex() = %v, want %v", got, tt.want)
}
})
}
}
func TestService_filterAttestations(t *testing.T) {
tests := []struct {
name string
input []*slashertypes.IndexedAttestationWrapper
inputEpoch primitives.Epoch
wantedValid []*slashertypes.IndexedAttestationWrapper
wantedDeferred []*slashertypes.IndexedAttestationWrapper
wantedDropped int
}{
{
name: "Nil attestation input gets dropped",
input: make([]*slashertypes.IndexedAttestationWrapper, 1),
inputEpoch: 0,
wantedDropped: 1,
},
{
name: "Nil attestation data gets dropped",
input: []*slashertypes.IndexedAttestationWrapper{
{
IndexedAttestation: &ethpb.IndexedAttestation{},
},
},
inputEpoch: 0,
wantedDropped: 1,
},
{
name: "Nil attestation source and target gets dropped",
input: []*slashertypes.IndexedAttestationWrapper{
{
IndexedAttestation: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{},
},
},
},
inputEpoch: 0,
wantedDropped: 1,
},
{
name: "Nil attestation source and good target gets dropped",
input: []*slashertypes.IndexedAttestationWrapper{
{
IndexedAttestation: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{},
},
},
},
},
inputEpoch: 0,
wantedDropped: 1,
},
{
name: "Nil attestation target and good source gets dropped",
input: []*slashertypes.IndexedAttestationWrapper{
{
IndexedAttestation: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
},
},
},
},
inputEpoch: 0,
wantedDropped: 1,
},
{
name: "Source > target gets dropped",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 1, 0, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 0,
wantedDropped: 1,
},
{
name: "Source < target is valid",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 1,
wantedValid: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)),
},
wantedDropped: 0,
},
{
name: "Source == target is valid",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 1,
wantedValid: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, make([]byte, 32)),
},
wantedDropped: 0,
},
{
name: "Attestation from the future is deferred",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 1,
wantedDeferred: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, make([]byte, 32)),
},
wantedDropped: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := &Service{
params: DefaultParams(),
}
valid, deferred, numDropped := srv.filterAttestations(tt.input, tt.inputEpoch)
if len(tt.wantedValid) > 0 {
require.DeepEqual(t, tt.wantedValid, valid)
}
if len(tt.wantedDeferred) > 0 {
require.DeepEqual(t, tt.wantedDeferred, deferred)
}
require.DeepEqual(t, tt.wantedDropped, numDropped)
})
}
}
func Test_logSlashingEvent(t *testing.T) {
tests := []struct {
name string
slashing *ethpb.AttesterSlashing
}{
{
name: "Surrounding vote",
slashing: &ethpb.AttesterSlashing{
Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
},
},
{
name: "Surrounded vote",
slashing: &ethpb.AttesterSlashing{
Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
},
},
{
name: "Double vote",
slashing: &ethpb.AttesterSlashing{
Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hook := logTest.NewGlobal()
logAttesterSlashing(tt.slashing)
require.LogsContain(t, hook, "")
})
}
}
func Test_validateAttestationIntegrity(t *testing.T) {
tests := []struct {
name string
att *ethpb.IndexedAttestation
want bool
}{
{
name: "Nil attestation returns false",
att: nil,
want: false,
},
{
name: "Nil attestation data returns false",
att: &ethpb.IndexedAttestation{},
want: false,
},
{
name: "Nil attestation source and target returns false",
att: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{},
},
want: false,
},
{
name: "Nil attestation source and good target returns false",
att: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{},
},
},
want: false,
},
{
name: "Nil attestation target and good source returns false",
att: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
},
},
want: false,
},
{
name: "Source > target returns false",
att: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{
Epoch: 1,
},
Target: &ethpb.Checkpoint{
Epoch: 0,
},
},
},
want: false,
},
{
name: "Source == target returns false",
att: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{
Epoch: 1,
},
Target: &ethpb.Checkpoint{
Epoch: 1,
},
},
},
want: false,
},
{
name: "Source < target returns true",
att: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{
Epoch: 1,
},
Target: &ethpb.Checkpoint{
Epoch: 2,
},
},
},
want: true,
},
{
name: "Source 0 target 0 returns true (genesis epoch attestations)",
att: &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{
Epoch: 0,
},
Target: &ethpb.Checkpoint{
Epoch: 0,
},
},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := validateAttestationIntegrity(tt.att); got != tt.want {
t.Errorf("validateAttestationIntegrity() = %v, want %v", got, tt.want)
}
})
}
}
func Test_validateBlockHeaderIntegrity(t *testing.T) {
type args struct {
header *ethpb.SignedBeaconBlockHeader
}
fakeSig := make([]byte, 96)
copy(fakeSig, "hi")
tests := []struct {
name string
args args
want bool
}{
{
name: "nil header",
args: args{
header: nil,
},
want: false,
},
{
name: "nil inner header",
args: args{
header: &ethpb.SignedBeaconBlockHeader{},
},
want: false,
},
{
name: "bad signature 1",
args: args{
header: &ethpb.SignedBeaconBlockHeader{Header: &ethpb.BeaconBlockHeader{}, Signature: []byte("hi")},
},
want: false,
},
{
name: "bad signature 2",
args: args{
header: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{},
Signature: make([]byte, fieldparams.BLSSignatureLength+1),
},
},
want: false,
},
{
name: "empty signature",
args: args{
header: &ethpb.SignedBeaconBlockHeader{Header: &ethpb.BeaconBlockHeader{}},
},
want: false,
},
{
name: "OK",
args: args{
header: &ethpb.SignedBeaconBlockHeader{Header: &ethpb.BeaconBlockHeader{}, Signature: fakeSig},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := validateBlockHeaderIntegrity(tt.args.header); got != tt.want {
t.Errorf("validateBlockHeaderIntegrity() = %v, want %v", got, tt.want)
}
})
}
}
func Test_isDoubleProposal(t *testing.T) {
type args struct {
incomingSigningRoot [32]byte
existingSigningRoot [32]byte
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Existing signing root empty returns false",
args: args{
incomingSigningRoot: [32]byte{1},
existingSigningRoot: params.BeaconConfig().ZeroHash,
},
want: false,
},
{
name: "Existing signing root non-empty and equal to incoming returns false",
args: args{
incomingSigningRoot: [32]byte{1},
existingSigningRoot: [32]byte{1},
},
want: false,
},
{
name: "Existing signing root non-empty and not-equal to incoming returns true",
args: args{
incomingSigningRoot: [32]byte{1},
existingSigningRoot: [32]byte{2},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isDoubleProposal(tt.args.incomingSigningRoot, tt.args.existingSigningRoot); got != tt.want {
t.Errorf("isDoubleProposal() = %v, want %v", got, tt.want)
}
})
}
}