mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
Optimized Slasher Docs and Helpers (#9578)
* bring over helpers * slasher helpers pass tests * fix dead link * rem eth2 * gaz * params * gaz * builds Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>
This commit is contained in:
@@ -3,12 +3,18 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"helpers.go",
|
||||
"params.go",
|
||||
"service.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/slasher",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
deps = [
|
||||
"//beacon-chain/slasher/types:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_ferranbt_fastssz//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
@@ -17,11 +23,20 @@ go_library(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["params_test.go"],
|
||||
srcs = [
|
||||
"helpers_test.go",
|
||||
"params_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/slasher/types:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_ferranbt_fastssz//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
43
beacon-chain/slasher/doc.go
Normal file
43
beacon-chain/slasher/doc.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Package slasher defines an optimized implementation of Ethereum proof-of-stake slashing
|
||||
// detection, namely focused on catching "surround vote" slashable
|
||||
// offenses as explained here: https://blog.ethereum.org/2020/01/13/validated-staking-on-eth2-1-incentives/.
|
||||
//
|
||||
// Surround vote detection is a difficult problem if done naively, as slasher
|
||||
// needs to keep track of every single attestation by every single validator
|
||||
// in the network and be ready to efficiently detect whether incoming attestations
|
||||
// are slashable with respect to older ones. To do this, the Sigma Prime team
|
||||
// created an elaborate design document: https://hackmd.io/@sproul/min-max-slasher
|
||||
// offering an optimal solution.
|
||||
//
|
||||
// Attesting histories are kept for each validator in two separate arrays known
|
||||
// as min and max spans, which are explained in our design document:
|
||||
// https://hackmd.io/@prysmaticlabs/slasher.
|
||||
//
|
||||
// A regular pair of min and max spans for a validator look as follows
|
||||
// with length = H where H is the amount of epochs worth of history
|
||||
// we want to persist for slashing detection.
|
||||
//
|
||||
// validator_1_min_span = [2, 2, 2, ..., 2]
|
||||
// validator_1_max_span = [0, 0, 0, ..., 0]
|
||||
//
|
||||
// Instead of always dealing with length H arrays, which can be prohibitively
|
||||
// expensive to handle in memory, we split these arrays into chunks of length C.
|
||||
// For C = 3, for example, the 0th chunk of validator 1's min and max spans would look
|
||||
// as follows:
|
||||
//
|
||||
// validator_1_min_span_chunk_0 = [2, 2, 2]
|
||||
// validator_1_max_span_chunk_0 = [2, 2, 2]
|
||||
//
|
||||
// Next, on disk, we take chunks for K validators, and store them as flat slices.
|
||||
// For example, if H = 3, C = 3, and K = 3, then we can store 3 validators' chunks as a flat
|
||||
// slice as follows:
|
||||
//
|
||||
// val0 val1 val2
|
||||
// | | |
|
||||
// { } { } { }
|
||||
// [2, 2, 2, 2, 2, 2, 2, 2, 2]
|
||||
//
|
||||
// This is known as 2D chunking, pioneered by the Sigma Prime team here:
|
||||
// https://hackmd.io/@sproul/min-max-slasher. The parameters H, C, and K will be
|
||||
// used extensively throughout this package.
|
||||
package slasher
|
||||
140
beacon-chain/slasher/helpers.go
Normal file
140
beacon-chain/slasher/helpers.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package slasher
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/container/slice"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Group a list of attestations into batches by validator chunk index.
|
||||
// This way, we can detect on the batch of attestations for each validator chunk index
|
||||
// concurrently, and also allowing us to effectively use a single 2D chunk
|
||||
// for slashing detection through this logical grouping.
|
||||
func (s *Service) groupByValidatorChunkIndex(
|
||||
attestations []*slashertypes.IndexedAttestationWrapper,
|
||||
) map[uint64][]*slashertypes.IndexedAttestationWrapper {
|
||||
groupedAttestations := make(map[uint64][]*slashertypes.IndexedAttestationWrapper)
|
||||
for _, att := range attestations {
|
||||
validatorChunkIndices := make(map[uint64]bool)
|
||||
for _, validatorIdx := range att.IndexedAttestation.AttestingIndices {
|
||||
validatorChunkIndex := s.params.validatorChunkIndex(types.ValidatorIndex(validatorIdx))
|
||||
validatorChunkIndices[validatorChunkIndex] = true
|
||||
}
|
||||
for validatorChunkIndex := range validatorChunkIndices {
|
||||
groupedAttestations[validatorChunkIndex] = append(
|
||||
groupedAttestations[validatorChunkIndex],
|
||||
att,
|
||||
)
|
||||
}
|
||||
}
|
||||
return groupedAttestations
|
||||
}
|
||||
|
||||
// Group attestations by the chunk index their source epoch corresponds to.
|
||||
func (s *Service) groupByChunkIndex(
|
||||
attestations []*slashertypes.IndexedAttestationWrapper,
|
||||
) map[uint64][]*slashertypes.IndexedAttestationWrapper {
|
||||
attestationsByChunkIndex := make(map[uint64][]*slashertypes.IndexedAttestationWrapper)
|
||||
for _, att := range attestations {
|
||||
chunkIdx := s.params.chunkIndex(att.IndexedAttestation.Data.Source.Epoch)
|
||||
attestationsByChunkIndex[chunkIdx] = append(attestationsByChunkIndex[chunkIdx], att)
|
||||
}
|
||||
return attestationsByChunkIndex
|
||||
}
|
||||
|
||||
// This function returns a list of valid attestations, a list of attestations that are
|
||||
// valid in the future, and the number of attestations dropped.
|
||||
func (s *Service) filterAttestations(
|
||||
atts []*slashertypes.IndexedAttestationWrapper, currentEpoch types.Epoch,
|
||||
) (valid, validInFuture []*slashertypes.IndexedAttestationWrapper, numDropped int) {
|
||||
valid = make([]*slashertypes.IndexedAttestationWrapper, 0, len(atts))
|
||||
validInFuture = make([]*slashertypes.IndexedAttestationWrapper, 0, len(atts))
|
||||
|
||||
for _, attWrapper := range atts {
|
||||
if attWrapper == nil || !validateAttestationIntegrity(attWrapper.IndexedAttestation) {
|
||||
numDropped++
|
||||
continue
|
||||
}
|
||||
|
||||
// If an attestation's source is epoch is older than the max history length
|
||||
// we keep track of for slashing detection, we drop it.
|
||||
if attWrapper.IndexedAttestation.Data.Source.Epoch+s.params.historyLength <= currentEpoch {
|
||||
numDropped++
|
||||
continue
|
||||
}
|
||||
|
||||
// If an attestations's target epoch is in the future, we defer processing for later.
|
||||
if attWrapper.IndexedAttestation.Data.Target.Epoch > currentEpoch {
|
||||
validInFuture = append(validInFuture, attWrapper)
|
||||
} else {
|
||||
valid = append(valid, attWrapper)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validates the attestation data integrity, ensuring we have no nil values for
|
||||
// source, epoch, and that the source epoch of the attestation must be less than
|
||||
// the target epoch, which is a precondition for performing slashing detection.
|
||||
// This function also checks the attestation source epoch is within the history size
|
||||
// we keep track of for slashing detection.
|
||||
func validateAttestationIntegrity(att *ethpb.IndexedAttestation) bool {
|
||||
// If an attestation is malformed, we drop it.
|
||||
if att == nil ||
|
||||
att.Data == nil ||
|
||||
att.Data.Source == nil ||
|
||||
att.Data.Target == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
sourceEpoch := att.Data.Source.Epoch
|
||||
targetEpoch := att.Data.Target.Epoch
|
||||
|
||||
// The genesis epoch is a special case, since all attestations formed in it
|
||||
// will have source and target 0, and they should be considered valid.
|
||||
if sourceEpoch == 0 && targetEpoch == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// All valid attestations must have source epoch < target epoch.
|
||||
return sourceEpoch < targetEpoch
|
||||
}
|
||||
|
||||
func logAttesterSlashing(slashing *ethpb.AttesterSlashing) {
|
||||
indices := slice.IntersectionUint64(slashing.Attestation_1.AttestingIndices, slashing.Attestation_2.AttestingIndices)
|
||||
log.WithFields(logrus.Fields{
|
||||
"validatorIndex": indices,
|
||||
"prevSourceEpoch": slashing.Attestation_1.Data.Source.Epoch,
|
||||
"prevTargetEpoch": slashing.Attestation_1.Data.Target.Epoch,
|
||||
"sourceEpoch": slashing.Attestation_2.Data.Source.Epoch,
|
||||
"targetEpoch": slashing.Attestation_2.Data.Target.Epoch,
|
||||
}).Info("Attester slashing detected")
|
||||
}
|
||||
|
||||
func logProposerSlashing(slashing *ethpb.ProposerSlashing) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"validatorIndex": slashing.Header_1.Header.ProposerIndex,
|
||||
"slot": slashing.Header_1.Header.Slot,
|
||||
}).Info("Proposer slashing detected")
|
||||
}
|
||||
|
||||
// Turns a uint64 value to a string representation.
|
||||
func uintToString(val uint64) string {
|
||||
return strconv.FormatUint(val, 10)
|
||||
}
|
||||
|
||||
// If an existing signing root does not match an incoming proposal signing root,
|
||||
// we then have a double block proposer slashing event.
|
||||
func isDoubleProposal(incomingSigningRoot, existingSigningRoot [32]byte) bool {
|
||||
// If the existing signing root is the zero hash, we do not consider
|
||||
// this a double proposal.
|
||||
if existingSigningRoot == params.BeaconConfig().ZeroHash {
|
||||
return false
|
||||
}
|
||||
return incomingSigningRoot != existingSigningRoot
|
||||
}
|
||||
477
beacon-chain/slasher/helpers_test.go
Normal file
477
beacon-chain/slasher/helpers_test.go
Normal file
@@ -0,0 +1,477 @@
|
||||
package slasher
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/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{
|
||||
createAttestationWrapper(t, 0, 0, []uint64{0, 1}, nil),
|
||||
createAttestationWrapper(t, 0, 0, []uint64{0, 1}, nil),
|
||||
},
|
||||
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
|
||||
0: {
|
||||
createAttestationWrapper(t, 0, 0, []uint64{0, 1}, nil),
|
||||
createAttestationWrapper(t, 0, 0, []uint64{0, 1}, nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Groups single attestation belonging to multiple validator chunk",
|
||||
params: &Parameters{
|
||||
validatorChunkSize: 2,
|
||||
},
|
||||
atts: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 0, 0, []uint64{0, 2, 4}, nil),
|
||||
},
|
||||
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
|
||||
0: {
|
||||
createAttestationWrapper(t, 0, 0, []uint64{0, 2, 4}, nil),
|
||||
},
|
||||
1: {
|
||||
createAttestationWrapper(t, 0, 0, []uint64{0, 2, 4}, nil),
|
||||
},
|
||||
2: {
|
||||
createAttestationWrapper(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{
|
||||
createAttestationWrapper(t, 0, 0, nil, nil),
|
||||
createAttestationWrapper(t, 1, 0, nil, nil),
|
||||
},
|
||||
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
|
||||
0: {
|
||||
createAttestationWrapper(t, 0, 0, nil, nil),
|
||||
createAttestationWrapper(t, 1, 0, nil, nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Groups multiple attestations belonging to multiple chunks",
|
||||
params: &Parameters{
|
||||
chunkSize: 2,
|
||||
historyLength: 3,
|
||||
},
|
||||
atts: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 0, 0, nil, nil),
|
||||
createAttestationWrapper(t, 1, 0, nil, nil),
|
||||
createAttestationWrapper(t, 2, 0, nil, nil),
|
||||
},
|
||||
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
|
||||
0: {
|
||||
createAttestationWrapper(t, 0, 0, nil, nil),
|
||||
createAttestationWrapper(t, 1, 0, nil, nil),
|
||||
},
|
||||
1: {
|
||||
createAttestationWrapper(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 types.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: ðpb.IndexedAttestation{},
|
||||
},
|
||||
},
|
||||
inputEpoch: 0,
|
||||
wantedDropped: 1,
|
||||
},
|
||||
{
|
||||
name: "Nil attestation source and target gets dropped",
|
||||
input: []*slashertypes.IndexedAttestationWrapper{
|
||||
{
|
||||
IndexedAttestation: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputEpoch: 0,
|
||||
wantedDropped: 1,
|
||||
},
|
||||
{
|
||||
name: "Nil attestation source and good target gets dropped",
|
||||
input: []*slashertypes.IndexedAttestationWrapper{
|
||||
{
|
||||
IndexedAttestation: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Target: ðpb.Checkpoint{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputEpoch: 0,
|
||||
wantedDropped: 1,
|
||||
},
|
||||
{
|
||||
name: "Nil attestation target and good source gets dropped",
|
||||
input: []*slashertypes.IndexedAttestationWrapper{
|
||||
{
|
||||
IndexedAttestation: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputEpoch: 0,
|
||||
wantedDropped: 1,
|
||||
},
|
||||
{
|
||||
name: "Source > target gets dropped",
|
||||
input: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 1, 0, []uint64{1}, make([]byte, 32)),
|
||||
},
|
||||
inputEpoch: 0,
|
||||
wantedDropped: 1,
|
||||
},
|
||||
{
|
||||
name: "Source < target is valid",
|
||||
input: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 0, 1, []uint64{1}, make([]byte, 32)),
|
||||
},
|
||||
inputEpoch: 1,
|
||||
wantedValid: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 0, 1, []uint64{1}, make([]byte, 32)),
|
||||
},
|
||||
wantedDropped: 0,
|
||||
},
|
||||
{
|
||||
name: "Source == target is valid",
|
||||
input: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 0, 0, []uint64{1}, make([]byte, 32)),
|
||||
},
|
||||
inputEpoch: 1,
|
||||
wantedValid: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 0, 0, []uint64{1}, make([]byte, 32)),
|
||||
},
|
||||
wantedDropped: 0,
|
||||
},
|
||||
{
|
||||
name: "Attestation from the future is deferred",
|
||||
input: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(t, 0, 2, []uint64{1}, make([]byte, 32)),
|
||||
},
|
||||
inputEpoch: 1,
|
||||
wantedDeferred: []*slashertypes.IndexedAttestationWrapper{
|
||||
createAttestationWrapper(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: ðpb.AttesterSlashing{
|
||||
Attestation_1: createAttestationWrapper(t, 0, 0, nil, nil).IndexedAttestation,
|
||||
Attestation_2: createAttestationWrapper(t, 0, 0, nil, nil).IndexedAttestation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Surrounded vote",
|
||||
slashing: ðpb.AttesterSlashing{
|
||||
Attestation_1: createAttestationWrapper(t, 0, 0, nil, nil).IndexedAttestation,
|
||||
Attestation_2: createAttestationWrapper(t, 0, 0, nil, nil).IndexedAttestation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Double vote",
|
||||
slashing: ðpb.AttesterSlashing{
|
||||
Attestation_1: createAttestationWrapper(t, 0, 0, nil, nil).IndexedAttestation,
|
||||
Attestation_2: createAttestationWrapper(t, 0, 0, nil, nil).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: ðpb.IndexedAttestation{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Nil attestation source and target returns false",
|
||||
att: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Nil attestation source and good target returns false",
|
||||
att: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Target: ðpb.Checkpoint{},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Nil attestation target and good source returns false",
|
||||
att: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Source > target returns false",
|
||||
att: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Source == target returns false",
|
||||
att: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Source < target returns true",
|
||||
att: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Source 0 target 0 returns true (genesis epoch attestations)",
|
||||
att: ðpb.IndexedAttestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
},
|
||||
Target: ðpb.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_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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createAttestationWrapper(t testing.TB, source, target types.Epoch, indices []uint64, signingRoot []byte) *slashertypes.IndexedAttestationWrapper {
|
||||
data := ðpb.AttestationData{
|
||||
BeaconBlockRoot: bytesutil.PadTo(signingRoot, 32),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: source,
|
||||
Root: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: target,
|
||||
Root: params.BeaconConfig().ZeroHash[:],
|
||||
},
|
||||
}
|
||||
signRoot, err := data.HashTreeRoot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return &slashertypes.IndexedAttestationWrapper{
|
||||
IndexedAttestation: ðpb.IndexedAttestation{
|
||||
AttestingIndices: indices,
|
||||
Data: data,
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
},
|
||||
SigningRoot: signRoot,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user