mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
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:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 ðpb.BeaconBlockHeader{
|
||||
Slot: block.Slot,
|
||||
ProposerIndex: block.ProposerIndex,
|
||||
ParentRoot: block.ParentRoot,
|
||||
StateRoot: block.StateRoot,
|
||||
BodyRoot: bodyRoot[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
104
shared/blockutil/block_utils_test.go
Normal file
104
shared/blockutil/block_utils_test.go
Normal 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 := ð.BeaconBlock{
|
||||
Slot: 200,
|
||||
ProposerIndex: 2,
|
||||
ParentRoot: bytesutil.PadTo([]byte("parent root"), hashLen),
|
||||
StateRoot: bytesutil.PadTo([]byte("state root"), hashLen),
|
||||
Body: ð.BeaconBlockBody{
|
||||
Eth1Data: ð.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 := ð.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 := ð.SignedBeaconBlock{Block: ð.BeaconBlock{
|
||||
Slot: 200,
|
||||
ProposerIndex: 2,
|
||||
ParentRoot: bytesutil.PadTo([]byte("parent root"), hashLen),
|
||||
StateRoot: bytesutil.PadTo([]byte("state root"), hashLen),
|
||||
Body: ð.BeaconBlockBody{
|
||||
Eth1Data: ð.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 := ð.SignedBeaconBlockHeader{Header: ð.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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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 := ðpb.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 := ðpb.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) {
|
||||
|
||||
@@ -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 := ðpb.IndexedAttestation{
|
||||
AttestingIndices: []uint64{1, 2},
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 5,
|
||||
CommitteeIndex: 2,
|
||||
BeaconBlockRoot: []byte("great block"),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 4,
|
||||
Root: []byte("good source"),
|
||||
},
|
||||
Target: ðpb.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 := ðpb.IndexedAttestation{
|
||||
AttestingIndices: []uint64{1, 2},
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 5,
|
||||
CommitteeIndex: 2,
|
||||
BeaconBlockRoot: []byte("great block"),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 4,
|
||||
Root: []byte("good source"),
|
||||
},
|
||||
Target: ðpb.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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 := ðpb.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 := ðpb.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
|
||||
}
|
||||
|
||||
@@ -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 := ðpb.IndexedAttestation{
|
||||
AttestingIndices: []uint64{1, 2},
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 5,
|
||||
CommitteeIndex: 2,
|
||||
BeaconBlockRoot: []byte("great block"),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 4,
|
||||
Root: []byte("good source"),
|
||||
},
|
||||
Target: ðpb.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 := ðpb.IndexedAttestation{
|
||||
AttestingIndices: []uint64{1, 2},
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 5,
|
||||
CommitteeIndex: 2,
|
||||
BeaconBlockRoot: []byte("great block"),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 4,
|
||||
Root: []byte("good source"),
|
||||
},
|
||||
Target: ðpb.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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 := ð.IndexedAttestation{
|
||||
AttestingIndices: []uint64{1, 2},
|
||||
Data: ð.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 := ð.IndexedAttestation{
|
||||
AttestingIndices: []uint64{1, 2},
|
||||
Data: ð.AttestationData{
|
||||
Slot: 5,
|
||||
CommitteeIndex: 2,
|
||||
BeaconBlockRoot: []byte("great block"),
|
||||
Source: ð.Checkpoint{
|
||||
Epoch: 4,
|
||||
Root: []byte("good source"),
|
||||
},
|
||||
Target: ð.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 := ð.SignedBeaconBlockHeader{
|
||||
Header: ð.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 := ð.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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
17
validator/testing/BUILD.bazel
Normal file
17
validator/testing/BUILD.bazel
Normal 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
41
validator/testing/protector_mock.go
generated
Normal 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
81
validator/testing/slasher_mock.go
generated
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user