diff --git a/beacon-chain/core/blocks/attester_slashing.go b/beacon-chain/core/blocks/attester_slashing.go index 4b6c0f3de..e2f2b2e40 100644 --- a/beacon-chain/core/blocks/attester_slashing.go +++ b/beacon-chain/core/blocks/attester_slashing.go @@ -62,7 +62,7 @@ func ProcessAttesterSlashing( if err := VerifyAttesterSlashing(ctx, beaconState, slashing); err != nil { return nil, errors.Wrap(err, "could not verify attester slashing") } - slashableIndices := slashableAttesterIndices(slashing) + slashableIndices := SlashableAttesterIndices(slashing) sort.SliceStable(slashableIndices, func(i, j int) bool { return slashableIndices[i] < slashableIndices[j] }) @@ -152,7 +152,8 @@ func IsSlashableAttestationData(data1, data2 *ethpb.AttestationData) bool { return isDoubleVote || isSurroundVote } -func slashableAttesterIndices(slashing *ethpb.AttesterSlashing) []uint64 { +// SlashableAttesterIndices returns the intersection of attester indices from both attestations in this slashing. +func SlashableAttesterIndices(slashing *ethpb.AttesterSlashing) []uint64 { if slashing == nil || slashing.Attestation_1 == nil || slashing.Attestation_2 == nil { return nil } diff --git a/beacon-chain/core/blocks/block_operations_fuzz_test.go b/beacon-chain/core/blocks/block_operations_fuzz_test.go index c1a30f5e8..6e18710e4 100644 --- a/beacon-chain/core/blocks/block_operations_fuzz_test.go +++ b/beacon-chain/core/blocks/block_operations_fuzz_test.go @@ -245,7 +245,7 @@ func TestFuzzslashableAttesterIndices_10000(t *testing.T) { for i := 0; i < 10000; i++ { fuzzer.Fuzz(attesterSlashing) - slashableAttesterIndices(attesterSlashing) + SlashableAttesterIndices(attesterSlashing) } } diff --git a/beacon-chain/monitor/BUILD.bazel b/beacon-chain/monitor/BUILD.bazel new file mode 100644 index 000000000..9000edcd9 --- /dev/null +++ b/beacon-chain/monitor/BUILD.bazel @@ -0,0 +1,37 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "process_block.go", + "service.go", + ], + importpath = "github.com/prysmaticlabs/prysm/beacon-chain/monitor", + visibility = ["//beacon-chain:__subpackages__"], + deps = [ + "//beacon-chain/core/blocks:go_default_library", + "//encoding/bytesutil:go_default_library", + "//proto/prysm/v1alpha1/block:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "process_block_test.go", + "service_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//config/params:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//proto/prysm/v1alpha1/wrapper:go_default_library", + "//testing/require:go_default_library", + "//testing/util:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_sirupsen_logrus//hooks/test:go_default_library", + ], +) diff --git a/beacon-chain/monitor/doc.go b/beacon-chain/monitor/doc.go new file mode 100644 index 000000000..f34001476 --- /dev/null +++ b/beacon-chain/monitor/doc.go @@ -0,0 +1,7 @@ +/* +Package monitor defines a runtime service which receives +notifications triggered by events related to performance of tracked +validating keys. It then logs and emits metrics for a user to keep finely +detailed performance measures. +*/ +package monitor diff --git a/beacon-chain/monitor/process_block.go b/beacon-chain/monitor/process_block.go new file mode 100644 index 000000000..eeca75b4a --- /dev/null +++ b/beacon-chain/monitor/process_block.go @@ -0,0 +1,47 @@ +package monitor + +import ( + "fmt" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" + "github.com/sirupsen/logrus" +) + +// processSlashings logs the event of one of our tracked validators was slashed +func (s *Service) processSlashings(blk block.BeaconBlock) { + for _, slashing := range blk.Body().ProposerSlashings() { + idx := slashing.Header_1.Header.ProposerIndex + if s.TrackedIndex(idx) { + log.WithFields(logrus.Fields{ + "ProposerIndex": idx, + "Slot:": blk.Slot(), + "SlashingSlot": slashing.Header_1.Header.Slot, + "Root1": fmt.Sprintf("%#x", bytesutil.Trunc(slashing.Header_1.Header.BodyRoot)), + "Root2": fmt.Sprintf("%#x", bytesutil.Trunc(slashing.Header_2.Header.BodyRoot)), + }).Info("Proposer slashing was included") + } + } + + for _, slashing := range blk.Body().AttesterSlashings() { + for _, idx := range blocks.SlashableAttesterIndices(slashing) { + if s.TrackedIndex(types.ValidatorIndex(idx)) { + log.WithFields(logrus.Fields{ + "AttesterIndex": idx, + "Slot:": blk.Slot(), + "Slot1": slashing.Attestation_1.Data.Slot, + "Root1": fmt.Sprintf("%#x", bytesutil.Trunc(slashing.Attestation_1.Data.BeaconBlockRoot)), + "SourceEpoch1": slashing.Attestation_1.Data.Source.Epoch, + "TargetEpoch1": slashing.Attestation_1.Data.Target.Epoch, + "Slot2": slashing.Attestation_2.Data.Slot, + "Root2": fmt.Sprintf("%#x", bytesutil.Trunc(slashing.Attestation_2.Data.BeaconBlockRoot)), + "SourceEpoch2": slashing.Attestation_2.Data.Source.Epoch, + "TargetEpoch2": slashing.Attestation_2.Data.Target.Epoch, + }).Info("Attester slashing was included") + + } + } + } +} diff --git a/beacon-chain/monitor/process_block_test.go b/beacon-chain/monitor/process_block_test.go new file mode 100644 index 000000000..10290b576 --- /dev/null +++ b/beacon-chain/monitor/process_block_test.go @@ -0,0 +1,131 @@ +package monitor + +import ( + "testing" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/config/params" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper" + "github.com/prysmaticlabs/prysm/testing/require" + "github.com/prysmaticlabs/prysm/testing/util" + logTest "github.com/sirupsen/logrus/hooks/test" +) + +func TestProcessSlashings(t *testing.T) { + tests := []struct { + name string + block *ethpb.BeaconBlock + wantedErr string + }{ + { + name: "Proposer slashing a tracked index", + block: ðpb.BeaconBlock{ + Body: ðpb.BeaconBlockBody{ + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + ProposerIndex: 2, + Slot: params.BeaconConfig().SlotsPerEpoch + 1, + }, + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + ProposerIndex: 2, + Slot: 0, + }, + }, + }, + }, + }, + }, + wantedErr: "\"Proposer slashing was included\" ProposerIndex=2", + }, + { + name: "Proposer slashing an untracked index", + block: ðpb.BeaconBlock{ + Body: ðpb.BeaconBlockBody{ + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + ProposerIndex: 3, + Slot: params.BeaconConfig().SlotsPerEpoch + 4, + }, + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + ProposerIndex: 3, + Slot: 0, + }, + }, + }, + }, + }, + }, + wantedErr: "", + }, + { + name: "Attester slashing a tracked index", + block: ðpb.BeaconBlock{ + Body: ðpb.BeaconBlockBody{ + AttesterSlashings: []*ethpb.AttesterSlashing{ + { + Attestation_1: util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 1}, + }, + AttestingIndices: []uint64{1, 3, 4}, + }), + Attestation_2: util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + AttestingIndices: []uint64{1, 5, 6}, + }), + }, + }, + }, + }, + wantedErr: "\"Attester slashing was included\" AttesterIndex=1", + }, + { + name: "Attester slashing untracked index", + block: ðpb.BeaconBlock{ + Body: ðpb.BeaconBlockBody{ + AttesterSlashings: []*ethpb.AttesterSlashing{ + { + Attestation_1: util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 1}, + }, + AttestingIndices: []uint64{1, 3, 4}, + }), + Attestation_2: util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + AttestingIndices: []uint64{3, 5, 6}, + }), + }, + }, + }, + }, + wantedErr: "", + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hook := logTest.NewGlobal() + s := &Service{ + config: &ValidatorMonitorConfig{ + TrackedValidators: map[types.ValidatorIndex]interface{}{ + 1: nil, + 2: nil, + }, + }, + } + s.processSlashings(wrapper.WrappedPhase0BeaconBlock(tt.block)) + if tt.wantedErr != "" { + require.LogsContain(t, hook, tt.wantedErr) + } else { + require.LogsDoNotContain(t, hook, "slashing") + } + }) + } +} diff --git a/beacon-chain/monitor/service.go b/beacon-chain/monitor/service.go new file mode 100644 index 000000000..33c41d0f6 --- /dev/null +++ b/beacon-chain/monitor/service.go @@ -0,0 +1,30 @@ +package monitor + +import ( + types "github.com/prysmaticlabs/eth2-types" + "github.com/sirupsen/logrus" +) + +var ( + log = logrus.WithField("prefix", "monitor") +) + +// ValidatorMonitorConfig contains the list of validator indices that the +// monitor service tracks, as well as the event feed notifier that the +// monitor needs to subscribe. +type ValidatorMonitorConfig struct { + TrackedValidators map[types.ValidatorIndex]interface{} +} + +// Service is the main structure that tracks validators and reports logs and +// metrics of their performances throughout their lifetime. +type Service struct { + config *ValidatorMonitorConfig +} + +// TrackedIndex returns if the given validator index corresponds to one of the +// validators we follow +func (s *Service) TrackedIndex(idx types.ValidatorIndex) bool { + _, ok := s.config.TrackedValidators[idx] + return ok +} diff --git a/beacon-chain/monitor/service_test.go b/beacon-chain/monitor/service_test.go new file mode 100644 index 000000000..eaf231f1c --- /dev/null +++ b/beacon-chain/monitor/service_test.go @@ -0,0 +1,21 @@ +package monitor + +import ( + "testing" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestTrackedIndex(t *testing.T) { + s := &Service{ + config: &ValidatorMonitorConfig{ + TrackedValidators: map[types.ValidatorIndex]interface{}{ + 1: nil, + 2: nil, + }, + }, + } + require.Equal(t, s.TrackedIndex(types.ValidatorIndex(1)), true) + require.Equal(t, s.TrackedIndex(types.ValidatorIndex(3)), false) +}