External slashing protection not requiring signature (#6252)

* validation without signature
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* validation and update funcs
* Merge branch 'slashing_protection_no_sign' of github.com:prysmaticlabs/prysm into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge branch 'master' of github.com:prysmaticlabs/prysm into slashing_protection_no_sign
* Merge branch 'slashing_protection_no_sign' of github.com:prysmaticlabs/prysm into slashing_protection_no_sign
* change order
error handling
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* ivan feedback
* Merge branch 'slashing_protection_no_sign' of github.com:prysmaticlabs/prysm into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* add tests to blocks utils
* terence feedback
* reduce visibility
* Merge branch 'master' of github.com:prysmaticlabs/prysm into slashing_protection_no_sign

# Conflicts:
#	validator/client/polling/validator_attest.go
#	validator/client/polling/validator_propose.go
* fix metrics
* fix error
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* copy behaviour to streaming
* Merge branch 'slashing_protection_no_sign' of github.com:prysmaticlabs/prysm into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
* Merge refs/heads/master into slashing_protection_no_sign
This commit is contained in:
Shay Zluf
2020-06-23 19:46:48 +03:00
committed by GitHub
parent c417b00675
commit 96a110a193
18 changed files with 722 additions and 188 deletions

View File

@@ -1,3 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
@@ -11,3 +12,16 @@ go_library(
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["block_utils_test.go"],
embed = [":go_default_library"],
deps = [
"//beacon-chain/state/stateutil:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/params:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
],
)

View File

@@ -6,7 +6,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil"
)
// SignedBeaconBlockHeaderFromBlock function to retrieve block header from block.
// SignedBeaconBlockHeaderFromBlock function to retrieve signed block header from block.
func SignedBeaconBlockHeaderFromBlock(block *ethpb.SignedBeaconBlock) (*ethpb.SignedBeaconBlockHeader, error) {
bodyRoot, err := stateutil.BlockBodyRoot(block.Block.Body)
if err != nil {
@@ -23,3 +23,18 @@ func SignedBeaconBlockHeaderFromBlock(block *ethpb.SignedBeaconBlock) (*ethpb.Si
Signature: block.Signature,
}, nil
}
// BeaconBlockHeaderFromBlock function to retrieve block header from block.
func BeaconBlockHeaderFromBlock(block *ethpb.BeaconBlock) (*ethpb.BeaconBlockHeader, error) {
bodyRoot, err := stateutil.BlockBodyRoot(block.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to get body root of block")
}
return &ethpb.BeaconBlockHeader{
Slot: block.Slot,
ProposerIndex: block.ProposerIndex,
ParentRoot: block.ParentRoot,
StateRoot: block.StateRoot,
BodyRoot: bodyRoot[:],
}, nil
}

View File

@@ -0,0 +1,104 @@
package blockutil
import (
"reflect"
"testing"
"github.com/pkg/errors"
eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/params"
)
func TestBeaconBlockHeaderFromBlock(t *testing.T) {
hashLen := 32
blk := &eth.BeaconBlock{
Slot: 200,
ProposerIndex: 2,
ParentRoot: bytesutil.PadTo([]byte("parent root"), hashLen),
StateRoot: bytesutil.PadTo([]byte("state root"), hashLen),
Body: &eth.BeaconBlockBody{
Eth1Data: &eth.Eth1Data{
BlockHash: bytesutil.PadTo([]byte("block hash"), hashLen),
DepositRoot: bytesutil.PadTo([]byte("deposit root"), hashLen),
DepositCount: 1,
},
RandaoReveal: bytesutil.PadTo([]byte("randao"), params.BeaconConfig().BLSSignatureLength),
Graffiti: bytesutil.PadTo([]byte("teehee"), hashLen),
ProposerSlashings: []*eth.ProposerSlashing{},
AttesterSlashings: []*eth.AttesterSlashing{},
Attestations: []*eth.Attestation{},
Deposits: []*eth.Deposit{},
VoluntaryExits: []*eth.SignedVoluntaryExit{},
},
}
bodyRoot, err := stateutil.BlockBodyRoot(blk.Body)
if err != nil {
t.Fatal(errors.Wrap(err, "failed to get body root of block"))
}
want := &eth.BeaconBlockHeader{
Slot: blk.Slot,
ProposerIndex: blk.ProposerIndex,
ParentRoot: blk.ParentRoot,
StateRoot: blk.StateRoot,
BodyRoot: bodyRoot[:],
}
bh, err := BeaconBlockHeaderFromBlock(blk)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, bh) {
t.Errorf("BeaconBlockHeaderFromBlock() got = %v, want %v", bh, want)
}
}
func TestSignedBeaconBlockHeaderFromBlock(t *testing.T) {
hashLen := 32
blk := &eth.SignedBeaconBlock{Block: &eth.BeaconBlock{
Slot: 200,
ProposerIndex: 2,
ParentRoot: bytesutil.PadTo([]byte("parent root"), hashLen),
StateRoot: bytesutil.PadTo([]byte("state root"), hashLen),
Body: &eth.BeaconBlockBody{
Eth1Data: &eth.Eth1Data{
BlockHash: bytesutil.PadTo([]byte("block hash"), hashLen),
DepositRoot: bytesutil.PadTo([]byte("deposit root"), hashLen),
DepositCount: 1,
},
RandaoReveal: bytesutil.PadTo([]byte("randao"), params.BeaconConfig().BLSSignatureLength),
Graffiti: bytesutil.PadTo([]byte("teehee"), hashLen),
ProposerSlashings: []*eth.ProposerSlashing{},
AttesterSlashings: []*eth.AttesterSlashing{},
Attestations: []*eth.Attestation{},
Deposits: []*eth.Deposit{},
VoluntaryExits: []*eth.SignedVoluntaryExit{},
},
},
Signature: bytesutil.PadTo([]byte("signature"), params.BeaconConfig().BLSSignatureLength),
}
bodyRoot, err := stateutil.BlockBodyRoot(blk.Block.Body)
if err != nil {
t.Fatal(errors.Wrap(err, "failed to get body root of block"))
}
want := &eth.SignedBeaconBlockHeader{Header: &eth.BeaconBlockHeader{
Slot: blk.Block.Slot,
ProposerIndex: blk.Block.ProposerIndex,
ParentRoot: blk.Block.ParentRoot,
StateRoot: blk.Block.StateRoot,
BodyRoot: bodyRoot[:],
},
Signature: blk.Signature,
}
bh, err := SignedBeaconBlockHeaderFromBlock(blk)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, bh) {
t.Errorf("SignedBeaconBlockHeaderFromBlock() got = %v, want %v", bh, want)
}
}

View File

@@ -84,6 +84,7 @@ go_test(
"//validator/accounts:go_default_library",
"//validator/db:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/testing:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_hashicorp_golang_lru//:go_default_library",

View File

@@ -47,9 +47,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
log.Debug("Empty committee for validator duty, not attesting")
return
}
v.attesterHistoryByPubKeyLock.RLock()
attesterHistory := v.attesterHistoryByPubKey[pubKey]
v.attesterHistoryByPubKeyLock.RUnlock()
v.waitToSlotOneThird(ctx, slot)
@@ -65,18 +62,12 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
}
return
}
if featureconfig.Get().ProtectAttester {
if isNewAttSlashable(attesterHistory, data.Source.Epoch, data.Target.Epoch) {
log.WithFields(logrus.Fields{
"sourceEpoch": data.Source.Epoch,
"targetEpoch": data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
}
return
}
indexedAtt := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{duty.ValidatorIndex},
Data: data,
}
if err := v.preSigningValidations(ctx, indexedAtt, pubKey); err != nil {
return
}
sig, err := v.signAtt(ctx, pubKey, data)
@@ -113,23 +104,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
Signature: sig,
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
indexedAtt := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{duty.ValidatorIndex},
Data: data,
Signature: sig,
}
if !v.protector.VerifyAttestation(ctx, indexedAtt) {
log.WithFields(logrus.Fields{
"sourceEpoch": data.Source.Epoch,
"targetEpoch": data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected by external slasher service")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
attResp, err := v.validatorClient.ProposeAttestation(ctx, attestation)
if err != nil {
log.WithError(err).Error("Could not submit attestation to beacon node")
@@ -138,7 +112,14 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
}
return
}
indexedAtt.Signature = sig
if err := v.postSignatureUpdate(ctx, indexedAtt, pubKey); err != nil {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).Fatal("made a slashable attestation, found by external slasher service")
return
}
if err := v.saveAttesterIndexToData(data, duty.ValidatorIndex); err != nil {
log.WithError(err).Error("Could not save validator index for logging")
if v.emitAccountMetrics {
@@ -147,13 +128,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
return
}
if featureconfig.Get().ProtectAttester {
attesterHistory = markAttestationForTargetEpoch(attesterHistory, data.Source.Epoch, data.Target.Epoch)
v.attesterHistoryByPubKeyLock.Lock()
v.attesterHistoryByPubKey[pubKey] = attesterHistory
v.attesterHistoryByPubKeyLock.Unlock()
}
if v.emitAccountMetrics {
metrics.ValidatorAttestSuccessVec.WithLabelValues(fmtKey).Inc()
}
@@ -168,6 +142,62 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
trace.StringAttribute("bitfield", fmt.Sprintf("%#x", aggregationBitfield)),
)
}
func (v *validator) preSigningValidations(ctx context.Context, indexedAtt *ethpb.IndexedAttestation, pubKey [48]byte) error {
fmtKey := fmt.Sprintf("%#x", pubKey[:])
log := log.WithField("pubKey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:]))).WithField("slot", indexedAtt.Data.Slot)
if featureconfig.Get().ProtectAttester {
v.attesterHistoryByPubKeyLock.RLock()
attesterHistory := v.attesterHistoryByPubKey[pubKey]
v.attesterHistoryByPubKeyLock.RUnlock()
if isNewAttSlashable(attesterHistory, indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch) {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
}
return fmt.Errorf("sourceEpoch: %dtargetEpoch: %d Attempted to make a slashable attestation,"+
" rejected by local slasher protection", indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
}
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
if !v.protector.VerifyAttestation(ctx, indexedAtt) {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected by external slasher service")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return fmt.Errorf("sourceEpoch: %dtargetEpoch: %d Attempted to make a slashable attestation,"+
" rejected by external slasher service", indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
}
}
return nil
}
func (v *validator) postSignatureUpdate(ctx context.Context, indexedAtt *ethpb.IndexedAttestation, pubKey [48]byte) error {
fmtKey := fmt.Sprintf("%#x", pubKey[:])
if featureconfig.Get().ProtectAttester {
v.attesterHistoryByPubKeyLock.Lock()
attesterHistory := v.attesterHistoryByPubKey[pubKey]
attesterHistory = markAttestationForTargetEpoch(attesterHistory, indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
v.attesterHistoryByPubKey[pubKey] = attesterHistory
v.attesterHistoryByPubKeyLock.Unlock()
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
if !v.protector.CommitAttestation(ctx, indexedAtt) {
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return fmt.Errorf("made a slashable attestation, sourceEpoch: %dtargetEpoch: %d "+
" found by external slasher service", indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
}
}
return nil
}
// Given the validator public key, this gets the validator assignment.
func (v *validator) duty(pubKey [48]byte) (*ethpb.DutiesResponse_Duty, error) {

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"reflect"
"strings"
"sync"
"testing"
"time"
@@ -18,6 +19,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/roughtime"
"github.com/prysmaticlabs/prysm/shared/testutil"
mockSlasher "github.com/prysmaticlabs/prysm/validator/testing"
logTest "github.com/sirupsen/logrus/hooks/test"
"gopkg.in/d4l3k/messagediff.v1"
)
@@ -204,6 +206,84 @@ func TestAttestToBlockHead_BlocksDoubleAtt(t *testing.T) {
testutil.AssertLogsContain(t, hook, "Attempted to make a slashable attestation, rejected")
}
func TestPostSignatureUpdate(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: false,
SlasherProtection: true,
}
reset := featureconfig.InitWithReset(config)
defer reset()
validator, _, finish := setup(t)
defer finish()
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: []byte("great block"),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: []byte("good source"),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: []byte("good target"),
},
},
}
mockProtector := &mockSlasher.MockProtector{AllowAttestation: false}
validator.protector = mockProtector
err := validator.postSignatureUpdate(context.Background(), att, validatorPubKey)
if err == nil || !strings.Contains(err.Error(), "made a slashable attestation,") {
t.Fatalf("Expected error to be thrown when post signature update is detected as slashable. got: %v", err)
}
mockProtector.AllowAttestation = true
err = validator.postSignatureUpdate(context.Background(), att, validatorPubKey)
if err != nil {
t.Fatalf("Expected allowed attestation not to throw error. got: %v", err)
}
}
func TestPreSignatureValidation(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: false,
SlasherProtection: true,
}
reset := featureconfig.InitWithReset(config)
defer reset()
validator, _, finish := setup(t)
defer finish()
hook := logTest.NewGlobal()
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: []byte("great block"),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: []byte("good source"),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: []byte("good target"),
},
},
}
mockProtector := &mockSlasher.MockProtector{AllowAttestation: false}
validator.protector = mockProtector
err := validator.preSigningValidations(context.Background(), att, validatorPubKey)
if err == nil || !strings.Contains(err.Error(), "rejected by external slasher service") {
t.Fatal(err)
}
testutil.AssertLogsContain(t, hook, "Attempted to make a slashable attestation, rejected by external slasher service")
mockProtector.AllowAttestation = true
err = validator.preSigningValidations(context.Background(), att, validatorPubKey)
if err != nil {
t.Fatalf("Expected allowed attestation not to throw error. got: %v", err)
}
}
func TestAttestToBlockHead_BlocksSurroundAtt(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: true,

View File

@@ -83,6 +83,19 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
return
}
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
bh, err := blockutil.BeaconBlockHeaderFromBlock(b)
if err != nil {
log.WithError(err).Error("Failed to get block header from block")
}
if !v.protector.VerifyBlock(ctx, bh) {
log.WithField("epoch", epoch).Error("Tried to sign a double proposal, rejected by external slasher")
if v.emitAccountMetrics {
metrics.ValidatorProposeFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
// Sign returned block from beacon node
sig, err := v.signBlock(ctx, pubKey, epoch, b)
@@ -98,20 +111,6 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
Signature: sig,
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
bh, err := blockutil.SignedBeaconBlockHeaderFromBlock(blk)
if err != nil {
log.WithError(err).Error("Failed to get block header from block")
}
if !v.protector.VerifyBlock(ctx, bh) {
log.WithField("epoch", epoch).Error("Tried to sign a double proposal, rejected by external slasher")
if v.emitAccountMetrics {
metrics.ValidatorProposeFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
// Propose and broadcast block via beacon node
blkResp, err := v.validatorClient.ProposeBlock(ctx, blk)
if err != nil {
@@ -122,6 +121,19 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
return
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
sbh, err := blockutil.SignedBeaconBlockHeaderFromBlock(blk)
if err != nil {
log.WithError(err).Error("Failed to get block header from block")
}
if !v.protector.CommitBlock(ctx, sbh) {
log.WithField("epoch", epoch).Fatal("Tried to sign a double proposal, rejected by external slasher")
if v.emitAccountMetrics {
metrics.ValidatorProposeFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
if featureconfig.Get().ProtectProposer {
slotBits.SetBitAt(slot%params.BeaconConfig().SlotsPerEpoch, true)
if err := v.db.SaveProposalHistoryForEpoch(ctx, pubKey[:], epoch, slotBits); err != nil {

View File

@@ -86,6 +86,7 @@ go_test(
"//validator/accounts:go_default_library",
"//validator/db:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/testing:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_hashicorp_golang_lru//:go_default_library",

View File

@@ -46,9 +46,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
log.Debug("Empty committee for validator duty, not attesting")
return
}
v.attesterHistoryByPubKeyLock.RLock()
attesterHistory := v.attesterHistoryByPubKey[pubKey]
v.attesterHistoryByPubKeyLock.RUnlock()
v.waitToSlotOneThird(ctx, slot)
@@ -65,19 +62,13 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
return
}
if featureconfig.Get().ProtectAttester {
if isNewAttSlashable(attesterHistory, data.Source.Epoch, data.Target.Epoch) {
log.WithFields(logrus.Fields{
"sourceEpoch": data.Source.Epoch,
"targetEpoch": data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
}
return
}
indexedAtt := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{duty.ValidatorIndex},
Data: data,
}
if err := v.preSigningValidations(ctx, indexedAtt, pubKey); err != nil {
return
}
sig, err := v.signAtt(ctx, pubKey, data)
if err != nil {
log.WithError(err).Error("Could not sign attestation")
@@ -112,23 +103,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
Signature: sig,
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
indexedAtt := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{duty.ValidatorIndex},
Data: data,
Signature: sig,
}
if !v.protector.VerifyAttestation(ctx, indexedAtt) {
log.WithFields(logrus.Fields{
"sourceEpoch": data.Source.Epoch,
"targetEpoch": data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected by external slasher service")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
attResp, err := v.validatorClient.ProposeAttestation(ctx, attestation)
if err != nil {
log.WithError(err).Error("Could not submit attestation to beacon node")
@@ -137,6 +111,14 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
}
return
}
indexedAtt.Signature = sig
if err := v.postSignatureUpdate(ctx, indexedAtt, pubKey); err != nil {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).Fatal("made a slashable attestation, found by external slasher service")
return
}
if err := v.saveAttesterIndexToData(data, duty.ValidatorIndex); err != nil {
log.WithError(err).Error("Could not save validator index for logging")
@@ -146,13 +128,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
return
}
if featureconfig.Get().ProtectAttester {
attesterHistory = markAttestationForTargetEpoch(attesterHistory, data.Source.Epoch, data.Target.Epoch)
v.attesterHistoryByPubKeyLock.Lock()
v.attesterHistoryByPubKey[pubKey] = attesterHistory
v.attesterHistoryByPubKeyLock.Unlock()
}
if v.emitAccountMetrics {
metrics.ValidatorAttestSuccessVec.WithLabelValues(fmtKey).Inc()
}
@@ -288,3 +263,60 @@ func (v *validator) waitToSlotOneThird(ctx context.Context, slot uint64) {
finalTime := startTime.Add(delay)
time.Sleep(roughtime.Until(finalTime))
}
func (v *validator) preSigningValidations(ctx context.Context, indexedAtt *ethpb.IndexedAttestation, pubKey [48]byte) error {
fmtKey := fmt.Sprintf("%#x", pubKey[:])
log := log.WithField("pubKey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:]))).WithField("slot", indexedAtt.Data.Slot)
if featureconfig.Get().ProtectAttester {
v.attesterHistoryByPubKeyLock.RLock()
attesterHistory := v.attesterHistoryByPubKey[pubKey]
v.attesterHistoryByPubKeyLock.RUnlock()
if isNewAttSlashable(attesterHistory, indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch) {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
}
return fmt.Errorf("sourceEpoch: %dtargetEpoch: %d Attempted to make a slashable attestation,"+
" rejected by local slasher protection", indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
}
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
if !v.protector.VerifyAttestation(ctx, indexedAtt) {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).Error("Attempted to make a slashable attestation, rejected by external slasher service")
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return fmt.Errorf("sourceEpoch: %dtargetEpoch: %d Attempted to make a slashable attestation,"+
" rejected by external slasher service", indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
}
}
return nil
}
func (v *validator) postSignatureUpdate(ctx context.Context, indexedAtt *ethpb.IndexedAttestation, pubKey [48]byte) error {
fmtKey := fmt.Sprintf("%#x", pubKey[:])
if featureconfig.Get().ProtectAttester {
v.attesterHistoryByPubKeyLock.Lock()
attesterHistory := v.attesterHistoryByPubKey[pubKey]
attesterHistory = markAttestationForTargetEpoch(attesterHistory, indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
v.attesterHistoryByPubKey[pubKey] = attesterHistory
v.attesterHistoryByPubKeyLock.Unlock()
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
if !v.protector.CommitAttestation(ctx, indexedAtt) {
if v.emitAccountMetrics {
metrics.ValidatorAttestFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return fmt.Errorf("made a slashable attestation, sourceEpoch: %dtargetEpoch: %d "+
" found by external slasher service", indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch)
}
}
return nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"reflect"
"strings"
"sync"
"testing"
"time"
@@ -11,9 +12,6 @@ import (
"github.com/golang/mock/gomock"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/go-bitfield"
logTest "github.com/sirupsen/logrus/hooks/test"
"gopkg.in/d4l3k/messagediff.v1"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
slashpb "github.com/prysmaticlabs/prysm/proto/slashing"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
@@ -21,6 +19,9 @@ import (
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/roughtime"
"github.com/prysmaticlabs/prysm/shared/testutil"
mockSlasher "github.com/prysmaticlabs/prysm/validator/testing"
logTest "github.com/sirupsen/logrus/hooks/test"
"gopkg.in/d4l3k/messagediff.v1"
)
func TestRequestAttestation_ValidatorDutiesRequestFailure(t *testing.T) {
@@ -212,6 +213,84 @@ func TestAttestToBlockHead_BlocksDoubleAtt(t *testing.T) {
testutil.AssertLogsContain(t, hook, "Attempted to make a slashable attestation, rejected")
}
func TestPostSignatureUpdate(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: false,
SlasherProtection: true,
}
reset := featureconfig.InitWithReset(config)
defer reset()
validator, _, finish := setup(t)
defer finish()
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: []byte("great block"),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: []byte("good source"),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: []byte("good target"),
},
},
}
mockProtector := &mockSlasher.MockProtector{AllowAttestation: false}
validator.protector = mockProtector
err := validator.postSignatureUpdate(context.Background(), att, validatorPubKey)
if err == nil || !strings.Contains(err.Error(), "made a slashable attestation,") {
t.Fatalf("Expected error to be thrown when post signature update is detected as slashable. got: %v", err)
}
mockProtector.AllowAttestation = true
err = validator.postSignatureUpdate(context.Background(), att, validatorPubKey)
if err != nil {
t.Fatalf("Expected allowed attestation not to throw error. got: %v", err)
}
}
func TestPreSignatureValidation(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: false,
SlasherProtection: true,
}
reset := featureconfig.InitWithReset(config)
defer reset()
validator, _, finish := setup(t)
defer finish()
hook := logTest.NewGlobal()
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: []byte("great block"),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: []byte("good source"),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: []byte("good target"),
},
},
}
mockProtector := &mockSlasher.MockProtector{AllowAttestation: false}
validator.protector = mockProtector
err := validator.preSigningValidations(context.Background(), att, validatorPubKey)
if err == nil || !strings.Contains(err.Error(), "rejected by external slasher service") {
t.Fatal(err)
}
testutil.AssertLogsContain(t, hook, "Attempted to make a slashable attestation, rejected by external slasher service")
mockProtector.AllowAttestation = true
err = validator.preSigningValidations(context.Background(), att, validatorPubKey)
if err != nil {
t.Fatalf("Expected allowed attestation not to throw error. got: %v", err)
}
}
func TestAttestToBlockHead_BlocksSurroundAtt(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: true,

View File

@@ -84,6 +84,20 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
}
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
bh, err := blockutil.BeaconBlockHeaderFromBlock(b)
if err != nil {
log.WithError(err).Error("Failed to get block header from block")
}
if !v.protector.VerifyBlock(ctx, bh) {
log.WithField("epoch", epoch).Error("Tried to sign a double proposal, rejected by external slasher")
if v.emitAccountMetrics {
metrics.ValidatorProposeFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
// Sign returned block from beacon node
sig, err := v.signBlock(ctx, pubKey, epoch, b)
if err != nil {
@@ -98,20 +112,6 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
Signature: sig,
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
bh, err := blockutil.SignedBeaconBlockHeaderFromBlock(blk)
if err != nil {
log.WithError(err).Error("Failed to get block header from block")
}
if !v.protector.VerifyBlock(ctx, bh) {
log.WithField("epoch", epoch).Error("Tried to sign a double proposal, rejected by external slasher")
if v.emitAccountMetrics {
metrics.ValidatorProposeFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
// Propose and broadcast block via beacon node
blkResp, err := v.validatorClient.ProposeBlock(ctx, blk)
if err != nil {
@@ -122,6 +122,20 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
return
}
if featureconfig.Get().SlasherProtection && v.protector != nil {
sbh, err := blockutil.SignedBeaconBlockHeaderFromBlock(blk)
if err != nil {
log.WithError(err).Error("Failed to get block header from block")
}
if !v.protector.CommitBlock(ctx, sbh) {
log.WithField("epoch", epoch).Fatal("Tried to sign a double proposal, rejected by external slasher")
if v.emitAccountMetrics {
metrics.ValidatorProposeFailVecSlasher.WithLabelValues(fmtKey).Inc()
}
return
}
}
if featureconfig.Get().ProtectProposer {
slotBits.SetBitAt(slot%params.BeaconConfig().SlotsPerEpoch, true)
if err := v.db.SaveProposalHistoryForEpoch(ctx, pubKey[:], epoch, slotBits); err != nil {

View File

@@ -31,9 +31,7 @@ go_test(
srcs = ["external_test.go"],
embed = [":go_default_library"],
deps = [
"//proto/slashing:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"//validator/testing:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)

View File

@@ -7,20 +7,48 @@ import (
log "github.com/sirupsen/logrus"
)
// VerifyBlock implements the slashing protection for block proposals.
func (s *Service) VerifyBlock(ctx context.Context, blockHeader *ethpb.SignedBeaconBlockHeader) bool {
// CommitBlock this function is part of slashing protection for block proposals it performs
// validation and db update.
func (s *Service) CommitBlock(ctx context.Context, blockHeader *ethpb.SignedBeaconBlockHeader) bool {
ps, err := s.slasherClient.IsSlashableBlock(ctx, blockHeader)
if err != nil {
log.Warnf("External slashing block protection returned an error: %v", err)
}
if ps != nil && ps.ProposerSlashing != nil {
log.Warn("External slashing proposal protection found the block to be slashable")
return false
}
return true
}
// VerifyAttestation implements the slashing protection for attestations.
// VerifyBlock this function is part of slashing protection for block proposals it performs
// validation without db update.
func (s *Service) VerifyBlock(ctx context.Context, blockHeader *ethpb.BeaconBlockHeader) bool {
slashable, err := s.slasherClient.IsSlashableBlockNoUpdate(ctx, blockHeader)
if err != nil {
log.Warnf("External slashing block protection returned an error: %v", err)
}
if slashable.Slashable {
log.Warn("External slashing proposal protection found the block to be slashable")
}
return !slashable.Slashable
}
// VerifyAttestation implements the slashing protection for attestations without db update.
func (s *Service) VerifyAttestation(ctx context.Context, attestation *ethpb.IndexedAttestation) bool {
slashable, err := s.slasherClient.IsSlashableAttestationNoUpdate(ctx, attestation)
if err != nil {
log.Warnf("External slashing attestation protection returned an error: %v", err)
}
if slashable.Slashable {
log.Warn("External slashing attestation protection found the attestation to be slashable")
}
return !slashable.Slashable
}
// CommitAttestation implements the slashing protection for attestations it performs
// validation and db update.
func (s *Service) CommitAttestation(ctx context.Context, attestation *ethpb.IndexedAttestation) bool {
as, err := s.slasherClient.IsSlashableAttestation(ctx, attestation)
if err != nil {
log.Warnf("External slashing attestation protection returned an error: %v", err)

View File

@@ -2,74 +2,14 @@ package slashingprotection
import (
"context"
"errors"
"testing"
"github.com/gogo/protobuf/proto"
eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
slashpb "github.com/prysmaticlabs/prysm/proto/slashing"
"google.golang.org/grpc"
mockSlasher "github.com/prysmaticlabs/prysm/validator/testing"
)
type mockSlasher struct {
slashAttestation bool
slashBlock bool
}
func (ms mockSlasher) IsSlashableAttestation(ctx context.Context, in *eth.IndexedAttestation, opts ...grpc.CallOption) (*slashpb.AttesterSlashingResponse, error) {
if ms.slashAttestation {
slashingAtt, ok := proto.Clone(in).(*eth.IndexedAttestation)
if !ok {
return nil, errors.New("object is not of type *eth.IndexedAttestation")
}
slashingAtt.Data.BeaconBlockRoot = []byte("slashing")
slashings := []*eth.AttesterSlashing{{
Attestation_1: in,
Attestation_2: slashingAtt,
},
}
return &slashpb.AttesterSlashingResponse{
AttesterSlashing: slashings,
}, nil
}
return nil, nil
}
func (ms mockSlasher) IsSlashableAttestationNoUpdate(ctx context.Context, in *eth.IndexedAttestation, opts ...grpc.CallOption) (*slashpb.Slashable, error) {
return &slashpb.Slashable{
Slashable: ms.slashAttestation,
}, nil
}
func (ms mockSlasher) IsSlashableBlock(ctx context.Context, in *eth.SignedBeaconBlockHeader, opts ...grpc.CallOption) (*slashpb.ProposerSlashingResponse, error) {
if ms.slashBlock {
slashingBlk, ok := proto.Clone(in).(*eth.SignedBeaconBlockHeader)
if !ok {
return nil, errors.New("object is not of type *eth.SignedBeaconBlockHeader")
}
slashingBlk.Header.BodyRoot = []byte("slashing")
slashings := []*eth.ProposerSlashing{{
Header_1: in,
Header_2: slashingBlk,
},
}
return &slashpb.ProposerSlashingResponse{
ProposerSlashing: slashings,
}, nil
}
return nil, nil
}
func (ms mockSlasher) IsSlashableBlockNoUpdate(ctx context.Context, in *eth.BeaconBlockHeader, opts ...grpc.CallOption) (*slashpb.Slashable, error) {
return &slashpb.Slashable{
Slashable: ms.slashBlock,
}, nil
}
func TestService_VerifyAttestation(t *testing.T) {
s := &Service{slasherClient: mockSlasher{slashAttestation: true}}
s := &Service{slasherClient: mockSlasher.MockSlasher{SlashAttestation: true}}
att := &eth.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &eth.AttestationData{
@@ -89,14 +29,41 @@ func TestService_VerifyAttestation(t *testing.T) {
if s.VerifyAttestation(context.Background(), att) {
t.Error("Expected verify attestation to fail verification")
}
s = &Service{slasherClient: mockSlasher{slashAttestation: false}}
s = &Service{slasherClient: mockSlasher.MockSlasher{SlashAttestation: false}}
if !s.VerifyAttestation(context.Background(), att) {
t.Error("Expected verify attestation to pass verification")
}
}
func TestService_VerifyBlock(t *testing.T) {
s := &Service{slasherClient: mockSlasher{slashBlock: true}}
func TestService_CommitAttestation(t *testing.T) {
s := &Service{slasherClient: mockSlasher.MockSlasher{SlashAttestation: true}}
att := &eth.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &eth.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: []byte("great block"),
Source: &eth.Checkpoint{
Epoch: 4,
Root: []byte("good source"),
},
Target: &eth.Checkpoint{
Epoch: 10,
Root: []byte("good target"),
},
},
}
if s.CommitAttestation(context.Background(), att) {
t.Error("Expected commit attestation to fail verification")
}
s = &Service{slasherClient: mockSlasher.MockSlasher{SlashAttestation: false}}
if !s.CommitAttestation(context.Background(), att) {
t.Error("Expected commit attestation to pass verification")
}
}
func TestService_CommitBlock(t *testing.T) {
s := &Service{slasherClient: mockSlasher.MockSlasher{SlashBlock: true}}
blk := &eth.SignedBeaconBlockHeader{
Header: &eth.BeaconBlockHeader{
Slot: 0,
@@ -106,11 +73,29 @@ func TestService_VerifyBlock(t *testing.T) {
BodyRoot: []byte("body"),
},
}
if s.VerifyBlock(context.Background(), blk) {
t.Error("Expected verify attestation to fail verification")
if s.CommitBlock(context.Background(), blk) {
t.Error("Expected commit block to fail verification")
}
s = &Service{slasherClient: mockSlasher{slashBlock: false}}
if !s.VerifyBlock(context.Background(), blk) {
t.Error("Expected verify attestation to pass verification")
s = &Service{slasherClient: mockSlasher.MockSlasher{SlashBlock: false}}
if !s.CommitBlock(context.Background(), blk) {
t.Error("Expected commit block to pass verification")
}
}
func TestService_VerifyBlock(t *testing.T) {
s := &Service{slasherClient: mockSlasher.MockSlasher{SlashBlock: true}}
blk := &eth.BeaconBlockHeader{
Slot: 0,
ProposerIndex: 0,
ParentRoot: []byte("parent"),
StateRoot: []byte("state"),
BodyRoot: []byte("body"),
}
if s.VerifyBlock(context.Background(), blk) {
t.Error("Expected verify block to fail verification")
}
s = &Service{slasherClient: mockSlasher.MockSlasher{SlashBlock: false}}
if !s.VerifyBlock(context.Background(), blk) {
t.Error("Expected verify block to pass verification")
}
}

View File

@@ -9,5 +9,7 @@ import (
// Protector interface defines the methods of the service that provides slashing protection.
type Protector interface {
VerifyAttestation(ctx context.Context, attestation *eth.IndexedAttestation) bool
VerifyBlock(ctx context.Context, blockHeader *eth.SignedBeaconBlockHeader) bool
CommitAttestation(ctx context.Context, attestation *eth.IndexedAttestation) bool
VerifyBlock(ctx context.Context, blockHeader *eth.BeaconBlockHeader) bool
CommitBlock(ctx context.Context, blockHeader *eth.SignedBeaconBlockHeader) bool
}

View File

@@ -0,0 +1,17 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"protector_mock.go",
"slasher_mock.go",
],
importpath = "github.com/prysmaticlabs/prysm/validator/testing",
visibility = ["//validator:__subpackages__"],
deps = [
"//proto/slashing:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)

41
validator/testing/protector_mock.go generated Normal file
View File

@@ -0,0 +1,41 @@
package testing
import (
"context"
eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
)
// MockProtector mocks the protector.
type MockProtector struct {
AllowAttestation bool
AllowBlock bool
VerifyAttestationCalled bool
CommitAttestationCalled bool
VerifyBlockCalled bool
CommitBlockCalled bool
}
// VerifyAttestation returns bool with allow attestation value.
func (mp MockProtector) VerifyAttestation(ctx context.Context, attestation *eth.IndexedAttestation) bool {
mp.VerifyAttestationCalled = true
return mp.AllowAttestation
}
// CommitAttestation returns bool with allow attestation value.
func (mp MockProtector) CommitAttestation(ctx context.Context, attestation *eth.IndexedAttestation) bool {
mp.CommitAttestationCalled = true
return mp.AllowAttestation
}
// VerifyBlock returns bool with allow block value.
func (mp MockProtector) VerifyBlock(ctx context.Context, blockHeader *eth.BeaconBlockHeader) bool {
mp.VerifyBlockCalled = true
return mp.AllowBlock
}
// CommitBlock returns bool with allow block value.
func (mp MockProtector) CommitBlock(ctx context.Context, blockHeader *eth.SignedBeaconBlockHeader) bool {
mp.CommitBlockCalled = true
return mp.AllowBlock
}

81
validator/testing/slasher_mock.go generated Normal file
View File

@@ -0,0 +1,81 @@
package testing
import (
"context"
"errors"
"github.com/golang/protobuf/proto"
eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
slashpb "github.com/prysmaticlabs/prysm/proto/slashing"
"google.golang.org/grpc"
)
// MockSlasher mocks the slasher rpc server.
type MockSlasher struct {
SlashAttestation bool
SlashBlock bool
IsSlashableAttestationCalled bool
IsSlashableAttestationNoUpdateCalled bool
IsSlashableBlockCalled bool
IsSlashableBlockNoUpdateCalled bool
}
// IsSlashableAttestation returns slashbale attestation if slash attestation is set to true.
func (ms MockSlasher) IsSlashableAttestation(ctx context.Context, in *eth.IndexedAttestation, opts ...grpc.CallOption) (*slashpb.AttesterSlashingResponse, error) {
ms.IsSlashableAttestationCalled = true
if ms.SlashAttestation {
slashingAtt, ok := proto.Clone(in).(*eth.IndexedAttestation)
if !ok {
return nil, errors.New("object is not of type *eth.IndexedAttestation")
}
slashingAtt.Data.BeaconBlockRoot = []byte("slashing")
slashings := []*eth.AttesterSlashing{{
Attestation_1: in,
Attestation_2: slashingAtt,
},
}
return &slashpb.AttesterSlashingResponse{
AttesterSlashing: slashings,
}, nil
}
return nil, nil
}
// IsSlashableAttestationNoUpdate returns slashbale if slash attestation is set to true.
func (ms MockSlasher) IsSlashableAttestationNoUpdate(ctx context.Context, in *eth.IndexedAttestation, opts ...grpc.CallOption) (*slashpb.Slashable, error) {
ms.IsSlashableAttestationNoUpdateCalled = true
return &slashpb.Slashable{
Slashable: ms.SlashAttestation,
}, nil
}
// IsSlashableBlock returns proposer slashing if slash block is set to true.
func (ms MockSlasher) IsSlashableBlock(ctx context.Context, in *eth.SignedBeaconBlockHeader, opts ...grpc.CallOption) (*slashpb.ProposerSlashingResponse, error) {
ms.IsSlashableBlockCalled = true
if ms.SlashBlock {
slashingBlk, ok := proto.Clone(in).(*eth.SignedBeaconBlockHeader)
if !ok {
return nil, errors.New("object is not of type *eth.SignedBeaconBlockHeader")
}
slashingBlk.Header.BodyRoot = []byte("slashing")
slashings := []*eth.ProposerSlashing{{
Header_1: in,
Header_2: slashingBlk,
},
}
return &slashpb.ProposerSlashingResponse{
ProposerSlashing: slashings,
}, nil
}
return nil, nil
}
// IsSlashableBlockNoUpdate returns slashbale if slash block is set to true.
func (ms MockSlasher) IsSlashableBlockNoUpdate(ctx context.Context, in *eth.BeaconBlockHeader, opts ...grpc.CallOption) (*slashpb.Slashable, error) {
ms.IsSlashableBlockNoUpdateCalled = true
return &slashpb.Slashable{
Slashable: ms.SlashBlock,
}, nil
}