mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
Compare commits
8 Commits
c6c9414d8b
...
slasher-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
978a061635 | ||
|
|
4e3a344948 | ||
|
|
5709840298 | ||
|
|
fb315c8f56 | ||
|
|
13beae5787 | ||
|
|
cf50ba4d64 | ||
|
|
5d04b7b9b2 | ||
|
|
8d736fec74 |
@@ -26,6 +26,7 @@ go_library(
|
||||
],
|
||||
deps = [
|
||||
"//async:go_default_library",
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/cache/depositcache:go_default_library",
|
||||
"//beacon-chain/core:go_default_library",
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/monitoring/tracing"
|
||||
ethpbv1 "github.com/prysmaticlabs/prysm/proto/eth/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/attestation"
|
||||
@@ -106,6 +107,32 @@ func (s *Service) onBlock(ctx context.Context, signed block.SignedBeaconBlock, b
|
||||
return err
|
||||
}
|
||||
|
||||
// If slasher is configured, forward the attestations in the block via
|
||||
// an event feed for processing.
|
||||
if features.Get().EnableSlasher {
|
||||
// Feed the indexed attestation to slasher if enabled. This action
|
||||
// is done in the background to avoid adding more load to this critical code path.
|
||||
go func() {
|
||||
for _, att := range signed.Block().Body().Attestations() {
|
||||
committee, err := helpers.BeaconCommitteeFromState(ctx, preState, att.Data.Slot, att.Data.CommitteeIndex)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get attestation committee")
|
||||
tracing.AnnotateError(span, err)
|
||||
return
|
||||
}
|
||||
// Using a different context to prevent timeouts as this operation can be expensive
|
||||
// and we want to avoid affecting the critical code path.
|
||||
indexedAtt, err := attestation.ConvertToIndexed(context.TODO(), att, committee)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not convert to indexed attestation")
|
||||
tracing.AnnotateError(span, err)
|
||||
return
|
||||
}
|
||||
s.cfg.SlasherAttestationsFeed.Send(indexedAtt)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Updating next slot state cache can happen in the background. It shouldn't block rest of the process.
|
||||
if features.Get().EnableNextSlotStateCache {
|
||||
go func() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core"
|
||||
@@ -82,6 +83,7 @@ type Config struct {
|
||||
ForkChoiceStore f.ForkChoicer
|
||||
AttService *attestations.Service
|
||||
StateGen *stategen.State
|
||||
SlasherAttestationsFeed *event.Feed
|
||||
WeakSubjectivityCheckpt *ethpb.Checkpoint
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = ["mock.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//testing/endtoend:__subpackages__",
|
||||
"//testing/fuzz:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
|
||||
3
beacon-chain/cache/committees.go
vendored
3
beacon-chain/cache/committees.go
vendored
@@ -10,6 +10,9 @@ import (
|
||||
// a Committee struct.
|
||||
var ErrNotCommittee = errors.New("object is not a committee struct")
|
||||
|
||||
// ErrNonCommitteeKey will be returned when the committee key does not exist in cache.
|
||||
var ErrNonCommitteeKey = errors.New("committee key does not exist")
|
||||
|
||||
// Committees defines the shuffled committees seed.
|
||||
type Committees struct {
|
||||
CommitteeCount uint64
|
||||
|
||||
@@ -72,7 +72,7 @@ func ProcessProposerSlashings(
|
||||
|
||||
// VerifyProposerSlashing verifies that the data provided from slashing is valid.
|
||||
func VerifyProposerSlashing(
|
||||
beaconState state.BeaconState,
|
||||
beaconState state.ReadOnlyBeaconState,
|
||||
slashing *ethpb.ProposerSlashing,
|
||||
) error {
|
||||
if slashing.Header_1 == nil || slashing.Header_1.Header == nil || slashing.Header_2 == nil || slashing.Header_2.Header == nil {
|
||||
|
||||
@@ -95,6 +95,21 @@ func VerifyBlockHeaderSignature(beaconState state.BeaconState, header *ethpb.Sig
|
||||
return signing.VerifyBlockHeaderSigningRoot(header.Header, proposerPubKey, header.Signature, domain)
|
||||
}
|
||||
|
||||
// VerifyBlockHeaderSignature verifies the proposer signature of a beacon block header.
|
||||
func VerifyBlockHeaderSignature(beaconState state.BeaconState, header *ethpb.SignedBeaconBlockHeader) error {
|
||||
currentEpoch := core.SlotToEpoch(beaconState.Slot())
|
||||
domain, err := helpers.Domain(beaconState.Fork(), currentEpoch, params.BeaconConfig().DomainBeaconProposer, beaconState.GenesisValidatorRoot())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proposer, err := beaconState.ValidatorAtIndex(header.Header.ProposerIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proposerPubKey := proposer.PublicKey
|
||||
return helpers.VerifyBlockHeaderSigningRoot(header.Header, proposerPubKey, header.Signature, domain)
|
||||
}
|
||||
|
||||
// VerifyBlockSignatureUsingCurrentFork verifies the proposer signature of a beacon block. This differs
|
||||
// from the above method by not using fork data from the state and instead retrieving it
|
||||
// via the respective epoch.
|
||||
|
||||
@@ -64,7 +64,7 @@ func signingData(rootFunc func() ([32]byte, error), domain []byte) ([32]byte, er
|
||||
}
|
||||
|
||||
// ComputeDomainVerifySigningRoot computes domain and verifies signing root of an object given the beacon state, validator index and signature.
|
||||
func ComputeDomainVerifySigningRoot(st state.BeaconState, index types.ValidatorIndex, epoch types.Epoch, obj fssz.HashRoot, domain [4]byte, sig []byte) error {
|
||||
func ComputeDomainVerifySigningRoot(st state.ReadOnlyBeaconState, index types.ValidatorIndex, epoch types.Epoch, obj fssz.HashRoot, domain [4]byte, sig []byte) error {
|
||||
v, err := st.ValidatorAtIndex(index)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//testing/endtoend:__subpackages__",
|
||||
"//testing/fuzz:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
|
||||
@@ -14,17 +14,17 @@ type PoolMock struct {
|
||||
}
|
||||
|
||||
// PendingAttesterSlashings --
|
||||
func (m *PoolMock) PendingAttesterSlashings(_ context.Context, _ state.ReadOnlyBeaconState, _ bool) []*ethpb.AttesterSlashing {
|
||||
func (m *PoolMock) PendingAttesterSlashings(_ context.Context, _ state.BeaconState, _ bool) []*ethpb.AttesterSlashing {
|
||||
return m.PendingAttSlashings
|
||||
}
|
||||
|
||||
// PendingProposerSlashings --
|
||||
func (m *PoolMock) PendingProposerSlashings(_ context.Context, _ state.ReadOnlyBeaconState, _ bool) []*ethpb.ProposerSlashing {
|
||||
func (m *PoolMock) PendingProposerSlashings(_ context.Context, _ state.BeaconState, _ bool) []*ethpb.ProposerSlashing {
|
||||
return m.PendingPropSlashings
|
||||
}
|
||||
|
||||
// InsertAttesterSlashing --
|
||||
func (m *PoolMock) InsertAttesterSlashing(_ context.Context, _ state.ReadOnlyBeaconState, slashing *ethpb.AttesterSlashing) error {
|
||||
func (m *PoolMock) InsertAttesterSlashing(_ context.Context, _ state.BeaconState, slashing *ethpb.AttesterSlashing) error {
|
||||
m.PendingAttSlashings = append(m.PendingAttSlashings, slashing)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewPool() *Pool {
|
||||
// PendingAttesterSlashings returns attester slashings that are able to be included into a block.
|
||||
// This method will return the amount of pending attester slashings for a block transition unless parameter `noLimit` is true
|
||||
// to indicate the request is for noLimit pending items.
|
||||
func (p *Pool) PendingAttesterSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []*ethpb.AttesterSlashing {
|
||||
func (p *Pool) PendingAttesterSlashings(ctx context.Context, state state.BeaconState, noLimit bool) []*ethpb.AttesterSlashing {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
ctx, span := trace.StartSpan(ctx, "operations.PendingAttesterSlashing")
|
||||
@@ -77,7 +77,7 @@ func (p *Pool) PendingAttesterSlashings(ctx context.Context, state state.ReadOnl
|
||||
// PendingProposerSlashings returns proposer slashings that are able to be included into a block.
|
||||
// This method will return the amount of pending proposer slashings for a block transition unless the `noLimit` parameter
|
||||
// is set to true to indicate the request is for noLimit pending items.
|
||||
func (p *Pool) PendingProposerSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []*ethpb.ProposerSlashing {
|
||||
func (p *Pool) PendingProposerSlashings(ctx context.Context, state state.BeaconState, noLimit bool) []*ethpb.ProposerSlashing {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
ctx, span := trace.StartSpan(ctx, "operations.PendingProposerSlashing")
|
||||
@@ -117,7 +117,7 @@ func (p *Pool) PendingProposerSlashings(ctx context.Context, state state.ReadOnl
|
||||
// has been included into a block recently, or the validator is already exited.
|
||||
func (p *Pool) InsertAttesterSlashing(
|
||||
ctx context.Context,
|
||||
state state.ReadOnlyBeaconState,
|
||||
state state.BeaconState,
|
||||
slashing *ethpb.AttesterSlashing,
|
||||
) error {
|
||||
p.lock.Lock()
|
||||
@@ -259,7 +259,7 @@ func (p *Pool) MarkIncludedProposerSlashing(ps *ethpb.ProposerSlashing) {
|
||||
// has been recently included in the pool, then it checks if the validator is slashable.
|
||||
// Note: this method requires caller to hold the lock.
|
||||
func (p *Pool) validatorSlashingPreconditionCheck(
|
||||
state state.ReadOnlyBeaconState,
|
||||
state state.BeaconState,
|
||||
valIdx types.ValidatorIndex,
|
||||
) (bool, error) {
|
||||
if !mutexasserts.RWMutexLocked(&p.lock) && !mutexasserts.RWMutexRLocked(&p.lock) {
|
||||
|
||||
@@ -6,6 +6,13 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = PoolManager(&Pool{})
|
||||
_ = PoolInserter(&Pool{})
|
||||
_ = PoolManager(&PoolMock{})
|
||||
_ = PoolInserter(&PoolMock{})
|
||||
)
|
||||
|
||||
func TestPool_validatorSlashingPreconditionCheck_requiresLock(t *testing.T) {
|
||||
p := &Pool{}
|
||||
_, err := p.validatorSlashingPreconditionCheck(nil, 0)
|
||||
|
||||
@@ -9,14 +9,11 @@ import (
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// PoolManager maintains a pool of pending and recently included attester and proposer slashings.
|
||||
// This pool is used by proposers to insert data into new blocks.
|
||||
type PoolManager interface {
|
||||
PendingAttesterSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []*ethpb.AttesterSlashing
|
||||
PendingProposerSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []*ethpb.ProposerSlashing
|
||||
// PoolInserter is capable of inserting new slashing objects into the operations pool.
|
||||
type PoolInserter interface {
|
||||
InsertAttesterSlashing(
|
||||
ctx context.Context,
|
||||
state state.ReadOnlyBeaconState,
|
||||
state state.BeaconState,
|
||||
slashing *ethpb.AttesterSlashing,
|
||||
) error
|
||||
InsertProposerSlashing(
|
||||
@@ -24,6 +21,14 @@ type PoolManager interface {
|
||||
state state.BeaconState,
|
||||
slashing *ethpb.ProposerSlashing,
|
||||
) error
|
||||
}
|
||||
|
||||
// PoolManager maintains a pool of pending and recently included attester and proposer slashings.
|
||||
// This pool is used by proposers to insert data into new blocks.
|
||||
type PoolManager interface {
|
||||
PoolInserter
|
||||
PendingAttesterSlashings(ctx context.Context, state state.BeaconState, noLimit bool) []*ethpb.AttesterSlashing
|
||||
PendingProposerSlashings(ctx context.Context, state state.BeaconState, noLimit bool) []*ethpb.ProposerSlashing
|
||||
MarkIncludedAttesterSlashing(as *ethpb.AttesterSlashing)
|
||||
MarkIncludedProposerSlashing(ps *ethpb.ProposerSlashing)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
// GossipTypeMapping.
|
||||
var ErrMessageNotMapped = errors.New("message type is not mapped to a PubSub topic")
|
||||
|
||||
// Broadcast a message to the p2p network, the message is assumed to be
|
||||
// Broadcasts a message to the p2p network, the message is assumed to be
|
||||
// broadcasted to the current fork.
|
||||
func (s *Service) Broadcast(ctx context.Context, msg proto.Message) error {
|
||||
ctx, span := trace.StartSpan(ctx, "p2p.Broadcast")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/metadata"
|
||||
metadata "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/metadata"
|
||||
)
|
||||
|
||||
// MockMetadataProvider is a fake implementation of the MetadataProvider interface.
|
||||
|
||||
@@ -30,8 +30,10 @@ go_library(
|
||||
"//beacon-chain/rpc/prysm/v1alpha1/beacon:go_default_library",
|
||||
"//beacon-chain/rpc/prysm/v1alpha1/debug:go_default_library",
|
||||
"//beacon-chain/rpc/prysm/v1alpha1/node:go_default_library",
|
||||
"//beacon-chain/rpc/prysm/v1alpha1/slasher:go_default_library",
|
||||
"//beacon-chain/rpc/prysm/v1alpha1/validator:go_default_library",
|
||||
"//beacon-chain/rpc/statefetcher:go_default_library",
|
||||
"//beacon-chain/slasher:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
|
||||
29
beacon-chain/rpc/prysm/v1alpha1/slasher/BUILD.bazel
Normal file
29
beacon-chain/rpc/prysm/v1alpha1/slasher/BUILD.bazel
Normal file
@@ -0,0 +1,29 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attestations.go",
|
||||
"blocks.go",
|
||||
"server.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/rpc/prysm/v1alpha1/slasher",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
deps = [
|
||||
"//beacon-chain/slasher:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["server_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/slasher:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_prysmaticlabs_prysm//shared/testutil/require:go_default_library",
|
||||
],
|
||||
)
|
||||
34
beacon-chain/rpc/prysm/v1alpha1/slasher/attestations.go
Normal file
34
beacon-chain/rpc/prysm/v1alpha1/slasher/attestations.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package slasher
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// IsSlashableAttestation returns an attester slashing if an input
|
||||
// attestation is found to be slashable.
|
||||
func (s *Server) IsSlashableAttestation(
|
||||
ctx context.Context, req *ethpb.IndexedAttestation,
|
||||
) (*ethpb.AttesterSlashingResponse, error) {
|
||||
attesterSlashings, err := s.SlashingChecker.IsSlashableAttestation(ctx, req)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not determine if attestation is slashable: %v", err)
|
||||
}
|
||||
if len(attesterSlashings) > 0 {
|
||||
return ðpb.AttesterSlashingResponse{
|
||||
AttesterSlashings: attesterSlashings,
|
||||
}, nil
|
||||
}
|
||||
return ðpb.AttesterSlashingResponse{}, nil
|
||||
}
|
||||
|
||||
// HighestAttestations returns the highest source and target epochs attested for
|
||||
// validator indices that have been observed by slasher.
|
||||
func (s *Server) HighestAttestations(
|
||||
ctx context.Context, req *ethpb.HighestAttestationRequest,
|
||||
) (*ethpb.HighestAttestationResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "Unimplemented")
|
||||
}
|
||||
23
beacon-chain/rpc/prysm/v1alpha1/slasher/blocks.go
Normal file
23
beacon-chain/rpc/prysm/v1alpha1/slasher/blocks.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package slasher
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// IsSlashableBlock returns a proposer slashing if an input
|
||||
// signed beacon block header is found to be slashable.
|
||||
func (s *Server) IsSlashableBlock(
|
||||
ctx context.Context, req *ethpb.SignedBeaconBlockHeader,
|
||||
) (*ethpb.ProposerSlashingResponse, error) {
|
||||
proposerSlashing, err := s.SlashingChecker.IsSlashableBlock(ctx, req)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not determine if block is slashable: %v", err)
|
||||
}
|
||||
return ðpb.ProposerSlashingResponse{
|
||||
ProposerSlashings: []*ethpb.ProposerSlashing{proposerSlashing},
|
||||
}, nil
|
||||
}
|
||||
12
beacon-chain/rpc/prysm/v1alpha1/slasher/server.go
Normal file
12
beacon-chain/rpc/prysm/v1alpha1/slasher/server.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Package slasher defines a gRPC server implementation of a slasher service
|
||||
// which allows for checking if attestations or blocks are slashable.
|
||||
package slasher
|
||||
|
||||
import (
|
||||
slasherservice "github.com/prysmaticlabs/prysm/beacon-chain/slasher"
|
||||
)
|
||||
|
||||
// Server defines a server implementation of the gRPC slasher service.
|
||||
type Server struct {
|
||||
SlashingChecker slasherservice.SlashingChecker
|
||||
}
|
||||
54
beacon-chain/rpc/prysm/v1alpha1/slasher/server_test.go
Normal file
54
beacon-chain/rpc/prysm/v1alpha1/slasher/server_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package slasher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/slasher"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
)
|
||||
|
||||
func TestServer_IsSlashableAttestation_SlashingFound(t *testing.T) {
|
||||
mockSlasher := &slasher.MockSlashingChecker{
|
||||
AttesterSlashingFound: true,
|
||||
}
|
||||
s := Server{SlashingChecker: mockSlasher}
|
||||
ctx := context.Background()
|
||||
slashing, err := s.IsSlashableAttestation(ctx, ðpb.IndexedAttestation{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(slashing.AttesterSlashings) > 0)
|
||||
}
|
||||
|
||||
func TestServer_IsSlashableAttestation_SlashingNotFound(t *testing.T) {
|
||||
mockSlasher := &slasher.MockSlashingChecker{
|
||||
AttesterSlashingFound: false,
|
||||
}
|
||||
s := Server{SlashingChecker: mockSlasher}
|
||||
ctx := context.Background()
|
||||
slashing, err := s.IsSlashableAttestation(ctx, ðpb.IndexedAttestation{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(slashing.AttesterSlashings) == 0)
|
||||
}
|
||||
|
||||
func TestServer_IsSlashableBlock_SlashingFound(t *testing.T) {
|
||||
mockSlasher := &slasher.MockSlashingChecker{
|
||||
ProposerSlashingFound: true,
|
||||
}
|
||||
s := Server{SlashingChecker: mockSlasher}
|
||||
ctx := context.Background()
|
||||
slashing, err := s.IsSlashableBlock(ctx, ðpb.SignedBeaconBlockHeader{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(slashing.ProposerSlashings) > 0)
|
||||
}
|
||||
|
||||
func TestServer_IsSlashableBlock_SlashingNotFound(t *testing.T) {
|
||||
mockSlasher := &slasher.MockSlashingChecker{
|
||||
ProposerSlashingFound: false,
|
||||
}
|
||||
s := Server{SlashingChecker: mockSlasher}
|
||||
ctx := context.Background()
|
||||
slashing, err := s.IsSlashableBlock(ctx, ðpb.SignedBeaconBlockHeader{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(slashing.ProposerSlashings) == 0)
|
||||
}
|
||||
@@ -48,6 +48,11 @@ type eth1DataSingleVote struct {
|
||||
blockHeight *big.Int
|
||||
}
|
||||
|
||||
type eth1DataAggregatedVote struct {
|
||||
data eth1DataSingleVote
|
||||
votes int
|
||||
}
|
||||
|
||||
// blockData required to create a beacon block.
|
||||
type blockData struct {
|
||||
ParentRoot []byte
|
||||
|
||||
@@ -34,8 +34,10 @@ import (
|
||||
beaconv1alpha1 "github.com/prysmaticlabs/prysm/beacon-chain/rpc/prysm/v1alpha1/beacon"
|
||||
debugv1alpha1 "github.com/prysmaticlabs/prysm/beacon-chain/rpc/prysm/v1alpha1/debug"
|
||||
nodev1alpha1 "github.com/prysmaticlabs/prysm/beacon-chain/rpc/prysm/v1alpha1/node"
|
||||
slasher2 "github.com/prysmaticlabs/prysm/beacon-chain/rpc/prysm/v1alpha1/slasher"
|
||||
validatorv1alpha1 "github.com/prysmaticlabs/prysm/beacon-chain/rpc/prysm/v1alpha1/validator"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/statefetcher"
|
||||
slasherservice "github.com/prysmaticlabs/prysm/beacon-chain/slasher"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
chainSync "github.com/prysmaticlabs/prysm/beacon-chain/sync"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
@@ -93,6 +95,7 @@ type Config struct {
|
||||
AttestationsPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SlashingsPool slashings.PoolManager
|
||||
SlashingChecker slasherservice.SlashingChecker
|
||||
SyncCommitteeObjectPool synccommittee.Pool
|
||||
SyncService chainSync.Checker
|
||||
Broadcaster p2p.Broadcaster
|
||||
@@ -234,6 +237,10 @@ func (s *Service) Start() {
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
}
|
||||
|
||||
slasherServer := &slasher2.Server{
|
||||
SlashingChecker: s.cfg.SlashingChecker,
|
||||
}
|
||||
|
||||
beaconChainServer := &beaconv1alpha1.Server{
|
||||
Ctx: s.ctx,
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
@@ -280,6 +287,7 @@ func (s *Service) Start() {
|
||||
ethpbv1alpha1.RegisterNodeServer(s.grpcServer, nodeServer)
|
||||
ethpbservice.RegisterBeaconNodeServer(s.grpcServer, nodeServerV1)
|
||||
ethpbv1alpha1.RegisterHealthServer(s.grpcServer, nodeServer)
|
||||
ethpbv1alpha1.RegisterSlasherServer(s.grpcServer, slasherServer)
|
||||
ethpbv1alpha1.RegisterBeaconChainServer(s.grpcServer, beaconChainServer)
|
||||
ethpbservice.RegisterBeaconChainServer(s.grpcServer, beaconChainServerV1)
|
||||
ethpbservice.RegisterEventsServer(s.grpcServer, &events.Server{
|
||||
|
||||
@@ -8,6 +8,7 @@ go_library(
|
||||
"detect_blocks.go",
|
||||
"doc.go",
|
||||
"helpers.go",
|
||||
"log.go",
|
||||
"metrics.go",
|
||||
"params.go",
|
||||
"process_slashings.go",
|
||||
@@ -22,12 +23,14 @@ go_library(
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/core:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/operations/slashings:go_default_library",
|
||||
"//beacon-chain/slasher/types:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
||||
5
beacon-chain/slasher/log.go
Normal file
5
beacon-chain/slasher/log.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package slasher
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
var log = logrus.WithField("prefix", "slasher")
|
||||
@@ -1,36 +1,157 @@
|
||||
// Package slasher implements slashing detection for eth2, able to catch slashable attestations
|
||||
// and proposals that it receives via two event feeds, respectively. Any found slashings
|
||||
// are then submitted to the beacon node's slashing operations pool. See the design document
|
||||
// here https://hackmd.io/@prysmaticlabs/slasher.
|
||||
package slasher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
||||
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("prefix", "slasher")
|
||||
|
||||
// ServiceConfig contains service dependencies for slasher.
|
||||
// ServiceConfig for the slasher service in the beacon node.
|
||||
// This struct allows us to specify required dependencies and
|
||||
// parameters for slasher to function as needed.
|
||||
type ServiceConfig struct {
|
||||
Database db.SlasherDatabase
|
||||
AttestationStateFetcher blockchain.AttestationStateFetcher
|
||||
IndexedAttestationsFeed *event.Feed
|
||||
BeaconBlockHeadersFeed *event.Feed
|
||||
StateGen stategen.StateManager
|
||||
SlashingPoolInserter slashings.PoolManager
|
||||
Database db.SlasherDatabase
|
||||
StateNotifier statefeed.Notifier
|
||||
AttestationStateFetcher blockchain.AttestationStateFetcher
|
||||
StateGen stategen.StateManager
|
||||
SlashingPoolInserter slashings.PoolInserter
|
||||
HeadStateFetcher blockchain.HeadFetcher
|
||||
SyncChecker sync.Checker
|
||||
}
|
||||
|
||||
// Service for running slasher mode in a beacon node.
|
||||
type Service struct {
|
||||
params *Parameters
|
||||
serviceCfg *ServiceConfig
|
||||
blksQueue *blocksQueue
|
||||
attsQueue *attestationsQueue
|
||||
genesisTime time.Time
|
||||
// SlashingChecker is an interface for defining services that the beacon node may interact with to provide slashing data.
|
||||
type SlashingChecker interface {
|
||||
IsSlashableBlock(ctx context.Context, proposal *ethpb.SignedBeaconBlockHeader) (*ethpb.ProposerSlashing, error)
|
||||
IsSlashableAttestation(ctx context.Context, attestation *ethpb.IndexedAttestation) ([]*ethpb.AttesterSlashing, error)
|
||||
}
|
||||
|
||||
// Service defining a slasher implementation as part of
|
||||
// the beacon node, able to detect eth2 slashable offenses.
|
||||
type Service struct {
|
||||
params *Parameters
|
||||
serviceCfg *ServiceConfig
|
||||
indexedAttsChan chan *ethpb.IndexedAttestation
|
||||
beaconBlockHeadersChan chan *ethpb.SignedBeaconBlockHeader
|
||||
attsQueue *attestationsQueue
|
||||
blksQueue *blocksQueue
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
slotTicker *slots.SlotTicker
|
||||
genesisTime time.Time
|
||||
}
|
||||
|
||||
// New instantiates a new slasher from configuration values.
|
||||
func New(ctx context.Context, srvCfg *ServiceConfig) (*Service, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &Service{
|
||||
params: DefaultParams(),
|
||||
serviceCfg: srvCfg,
|
||||
indexedAttsChan: make(chan *ethpb.IndexedAttestation, 1),
|
||||
beaconBlockHeadersChan: make(chan *ethpb.SignedBeaconBlockHeader, 1),
|
||||
attsQueue: newAttestationsQueue(),
|
||||
blksQueue: newBlocksQueue(),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start listening for received indexed attestations and blocks
|
||||
// and perform slashing detection on them.
|
||||
func (s *Service) Start() {
|
||||
go s.run()
|
||||
}
|
||||
|
||||
func (s *Service) run() {
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := s.serviceCfg.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
stateEvent := <-stateChannel
|
||||
|
||||
// Wait for us to receive the genesis time via a chain started notification.
|
||||
if stateEvent.Type == statefeed.ChainStarted {
|
||||
data, ok := stateEvent.Data.(*statefeed.ChainStartedData)
|
||||
if !ok {
|
||||
log.Error("Could not receive chain start notification, want *statefeed.ChainStartedData")
|
||||
return
|
||||
}
|
||||
s.genesisTime = data.StartTime
|
||||
log.WithField("genesisTime", s.genesisTime).Info("Starting slasher, received chain start event")
|
||||
} else if stateEvent.Type == statefeed.Initialized {
|
||||
// Alternatively, if the chain has already started, we then read the genesis
|
||||
// time value from this data.
|
||||
data, ok := stateEvent.Data.(*statefeed.InitializedData)
|
||||
if !ok {
|
||||
log.Error("Could not receive chain start notification, want *statefeed.ChainStartedData")
|
||||
return
|
||||
}
|
||||
s.genesisTime = data.StartTime
|
||||
log.WithField("genesisTime", s.genesisTime).Info("Starting slasher, chain already initialized")
|
||||
} else {
|
||||
// This should not happen.
|
||||
log.Error("Could start slasher, could not receive chain start event")
|
||||
return
|
||||
}
|
||||
|
||||
stateSub.Unsubscribe()
|
||||
secondsPerSlot := params.BeaconConfig().SecondsPerSlot
|
||||
s.slotTicker = slots.NewSlotTicker(s.genesisTime, secondsPerSlot)
|
||||
|
||||
s.waitForSync(s.genesisTime)
|
||||
|
||||
indexedAttsChan := make(chan *ethpb.IndexedAttestation, 1)
|
||||
beaconBlockHeadersChan := make(chan *ethpb.SignedBeaconBlockHeader, 1)
|
||||
log.Info("Completed chain sync, starting slashing detection")
|
||||
go s.processQueuedAttestations(s.ctx, s.slotTicker.C())
|
||||
go s.processQueuedBlocks(s.ctx, s.slotTicker.C())
|
||||
go s.receiveAttestations(s.ctx, indexedAttsChan)
|
||||
go s.receiveBlocks(s.ctx, beaconBlockHeadersChan)
|
||||
go s.pruneSlasherData(s.ctx, s.slotTicker.C())
|
||||
}
|
||||
|
||||
// Stop the slasher service.
|
||||
func (s *Service) Stop() error {
|
||||
s.cancel()
|
||||
if s.slotTicker != nil {
|
||||
s.slotTicker.Done()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status of the slasher service.
|
||||
func (s *Service) Status() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) waitForSync(genesisTime time.Time) {
|
||||
if slots.SlotsSinceGenesis(genesisTime) == 0 || !s.serviceCfg.SyncChecker.Syncing() {
|
||||
return
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-s.slotTicker.C():
|
||||
// If node is still syncing, do not operate slasher.
|
||||
if s.serviceCfg.SyncChecker.Syncing() {
|
||||
continue
|
||||
}
|
||||
return
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
beacon-chain/slasher/simulator/BUILD.bazel
Normal file
58
beacon-chain/slasher/simulator/BUILD.bazel
Normal file
@@ -0,0 +1,58 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attestation_generator.go",
|
||||
"block_generator.go",
|
||||
"simulator.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/slasher/simulator",
|
||||
visibility = [
|
||||
"//endtoend:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/core:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/operations/slashings:go_default_library",
|
||||
"//beacon-chain/slasher:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//time/slots: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 = [
|
||||
"attestation_generator_test.go",
|
||||
"block_generator_test.go",
|
||||
"simulator_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/slashings:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
177
beacon-chain/slasher/simulator/attestation_generator.go
Normal file
177
beacon-chain/slasher/simulator/attestation_generator.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package simulator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/signing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (s *Simulator) generateAttestationsForSlot(
|
||||
ctx context.Context, slot types.Slot,
|
||||
) ([]*ethpb.IndexedAttestation, []*ethpb.AttesterSlashing, error) {
|
||||
attestations := make([]*ethpb.IndexedAttestation, 0)
|
||||
slashings := make([]*ethpb.AttesterSlashing, 0)
|
||||
currentEpoch := core.SlotToEpoch(slot)
|
||||
|
||||
committeesPerSlot := helpers.SlotCommitteeCount(s.srvConfig.Params.NumValidators)
|
||||
valsPerCommittee := s.srvConfig.Params.NumValidators /
|
||||
(committeesPerSlot * uint64(s.srvConfig.Params.SlotsPerEpoch))
|
||||
valsPerSlot := committeesPerSlot * valsPerCommittee
|
||||
|
||||
var sourceEpoch types.Epoch = 0
|
||||
if currentEpoch != 0 {
|
||||
sourceEpoch = currentEpoch - 1
|
||||
}
|
||||
|
||||
var slashedIndices []uint64
|
||||
startIdx := valsPerSlot * uint64(slot%s.srvConfig.Params.SlotsPerEpoch)
|
||||
endIdx := startIdx + valsPerCommittee
|
||||
for c := types.CommitteeIndex(0); uint64(c) < committeesPerSlot; c++ {
|
||||
attData := ðpb.AttestationData{
|
||||
Slot: slot,
|
||||
CommitteeIndex: c,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("block"), 32),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: sourceEpoch,
|
||||
Root: bytesutil.PadTo([]byte("source"), 32),
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: currentEpoch,
|
||||
Root: bytesutil.PadTo([]byte("target"), 32),
|
||||
},
|
||||
}
|
||||
|
||||
valsPerAttestation := uint64(math.Floor(s.srvConfig.Params.AggregationPercent * float64(valsPerCommittee)))
|
||||
for i := startIdx; i < endIdx; i += valsPerAttestation {
|
||||
attEndIdx := i + valsPerAttestation
|
||||
if attEndIdx >= endIdx {
|
||||
attEndIdx = endIdx
|
||||
}
|
||||
indices := make([]uint64, 0, valsPerAttestation)
|
||||
for idx := i; idx < attEndIdx; idx++ {
|
||||
indices = append(indices, idx)
|
||||
}
|
||||
att := ðpb.IndexedAttestation{
|
||||
AttestingIndices: indices,
|
||||
Data: attData,
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
}
|
||||
beaconState, err := s.srvConfig.AttestationStateFetcher.AttestationTargetState(ctx, att.Data.Target)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Sign the attestation with a valid signature.
|
||||
aggSig, err := s.aggregateSigForAttestation(beaconState, att)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
att.Signature = aggSig.Marshal()
|
||||
|
||||
attestations = append(attestations, att)
|
||||
if rand.NewGenerator().Float64() < s.srvConfig.Params.AttesterSlashingProbab {
|
||||
slashableAtt := makeSlashableFromAtt(att, []uint64{indices[0]})
|
||||
aggSig, err := s.aggregateSigForAttestation(beaconState, slashableAtt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
slashableAtt.Signature = aggSig.Marshal()
|
||||
slashedIndices = append(slashedIndices, slashableAtt.AttestingIndices...)
|
||||
slashings = append(slashings, ðpb.AttesterSlashing{
|
||||
Attestation_1: att,
|
||||
Attestation_2: slashableAtt,
|
||||
})
|
||||
attestations = append(attestations, slashableAtt)
|
||||
}
|
||||
}
|
||||
startIdx += valsPerCommittee
|
||||
endIdx += valsPerCommittee
|
||||
}
|
||||
if len(slashedIndices) > 0 {
|
||||
log.WithFields(logrus.Fields{
|
||||
"amount": len(slashedIndices),
|
||||
"indices": slashedIndices,
|
||||
}).Infof("Slashable attestation made")
|
||||
}
|
||||
return attestations, slashings, nil
|
||||
}
|
||||
|
||||
func (s *Simulator) aggregateSigForAttestation(
|
||||
beaconState state.BeaconState, att *ethpb.IndexedAttestation,
|
||||
) (bls.Signature, error) {
|
||||
domain, err := signing.Domain(
|
||||
beaconState.Fork(),
|
||||
att.Data.Target.Epoch,
|
||||
params.BeaconConfig().DomainBeaconAttester,
|
||||
beaconState.GenesisValidatorRoot(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signingRoot, err := signing.ComputeSigningRoot(att.Data, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigs := make([]bls.Signature, len(att.AttestingIndices))
|
||||
for i, validatorIndex := range att.AttestingIndices {
|
||||
privKey := s.srvConfig.PrivateKeysByValidatorIndex[types.ValidatorIndex(validatorIndex)]
|
||||
sigs[i] = privKey.Sign(signingRoot[:])
|
||||
}
|
||||
return bls.AggregateSignatures(sigs), nil
|
||||
}
|
||||
|
||||
func makeSlashableFromAtt(att *ethpb.IndexedAttestation, indices []uint64) *ethpb.IndexedAttestation {
|
||||
if att.Data.Source.Epoch <= 2 {
|
||||
return makeDoubleVoteFromAtt(att, indices)
|
||||
}
|
||||
attData := ðpb.AttestationData{
|
||||
Slot: att.Data.Slot,
|
||||
CommitteeIndex: att.Data.CommitteeIndex,
|
||||
BeaconBlockRoot: att.Data.BeaconBlockRoot,
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: att.Data.Source.Epoch - 3,
|
||||
Root: att.Data.Source.Root,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: att.Data.Target.Epoch,
|
||||
Root: att.Data.Target.Root,
|
||||
},
|
||||
}
|
||||
return ðpb.IndexedAttestation{
|
||||
AttestingIndices: indices,
|
||||
Data: attData,
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
}
|
||||
}
|
||||
|
||||
func makeDoubleVoteFromAtt(att *ethpb.IndexedAttestation, indices []uint64) *ethpb.IndexedAttestation {
|
||||
attData := ðpb.AttestationData{
|
||||
Slot: att.Data.Slot,
|
||||
CommitteeIndex: att.Data.CommitteeIndex,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("slash me"), 32),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: att.Data.Source.Epoch,
|
||||
Root: att.Data.Source.Root,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: att.Data.Target.Epoch,
|
||||
Root: att.Data.Target.Root,
|
||||
},
|
||||
}
|
||||
return ðpb.IndexedAttestation{
|
||||
AttestingIndices: indices,
|
||||
Data: attData,
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
}
|
||||
}
|
||||
64
beacon-chain/slasher/simulator/attestation_generator_test.go
Normal file
64
beacon-chain/slasher/simulator/attestation_generator_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package simulator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/slashings"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
func TestGenerateAttestationsForSlot_Slashing(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
simParams := &Parameters{
|
||||
SecondsPerSlot: params.BeaconConfig().SecondsPerSlot,
|
||||
SlotsPerEpoch: params.BeaconConfig().SlotsPerEpoch,
|
||||
AggregationPercent: 1,
|
||||
NumValidators: 64,
|
||||
AttesterSlashingProbab: 1,
|
||||
}
|
||||
srv := setupService(t, simParams)
|
||||
|
||||
epoch3Atts, _, err := srv.generateAttestationsForSlot(ctx, params.BeaconConfig().SlotsPerEpoch*3)
|
||||
require.NoError(t, err)
|
||||
epoch4Atts, _, err := srv.generateAttestationsForSlot(ctx, params.BeaconConfig().SlotsPerEpoch*4)
|
||||
require.NoError(t, err)
|
||||
for i := 0; i < len(epoch3Atts); i += 2 {
|
||||
goodAtt := epoch3Atts[i]
|
||||
surroundAtt := epoch4Atts[i+1]
|
||||
require.Equal(t, true, slashings.IsSurround(surroundAtt, goodAtt))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateAttestationsForSlot_CorrectIndices(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
simParams := &Parameters{
|
||||
SecondsPerSlot: params.BeaconConfig().SecondsPerSlot,
|
||||
SlotsPerEpoch: params.BeaconConfig().SlotsPerEpoch,
|
||||
AggregationPercent: 1,
|
||||
NumValidators: 16384,
|
||||
AttesterSlashingProbab: 0,
|
||||
}
|
||||
srv := setupService(t, simParams)
|
||||
slot0Atts, _, err := srv.generateAttestationsForSlot(ctx, 0)
|
||||
require.NoError(t, err)
|
||||
slot1Atts, _, err := srv.generateAttestationsForSlot(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
slot2Atts, _, err := srv.generateAttestationsForSlot(ctx, 2)
|
||||
require.NoError(t, err)
|
||||
var validatorIndices []uint64
|
||||
for _, att := range append(slot0Atts, slot1Atts...) {
|
||||
validatorIndices = append(validatorIndices, att.AttestingIndices...)
|
||||
}
|
||||
for _, att := range slot2Atts {
|
||||
validatorIndices = append(validatorIndices, att.AttestingIndices...)
|
||||
}
|
||||
|
||||
// Making sure indices are one after the other for attestations.
|
||||
var validatorIndex uint64
|
||||
for _, ii := range validatorIndices {
|
||||
require.Equal(t, validatorIndex, ii)
|
||||
validatorIndex++
|
||||
}
|
||||
}
|
||||
98
beacon-chain/slasher/simulator/block_generator.go
Normal file
98
beacon-chain/slasher/simulator/block_generator.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package simulator
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/signing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
func (s *Simulator) generateBlockHeadersForSlot(
|
||||
ctx context.Context, slot types.Slot,
|
||||
) ([]*ethpb.SignedBeaconBlockHeader, []*ethpb.ProposerSlashing, error) {
|
||||
blocks := make([]*ethpb.SignedBeaconBlockHeader, 0)
|
||||
slashings := make([]*ethpb.ProposerSlashing, 0)
|
||||
proposer := rand.NewGenerator().Uint64() % s.srvConfig.Params.NumValidators
|
||||
|
||||
parentRoot := [32]byte{}
|
||||
beaconState, err := s.srvConfig.StateGen.StateByRoot(ctx, parentRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
block := ðpb.SignedBeaconBlockHeader{
|
||||
Header: ðpb.BeaconBlockHeader{
|
||||
Slot: slot,
|
||||
ProposerIndex: types.ValidatorIndex(proposer),
|
||||
ParentRoot: bytesutil.PadTo([]byte{}, 32),
|
||||
StateRoot: bytesutil.PadTo([]byte{}, 32),
|
||||
BodyRoot: bytesutil.PadTo([]byte("good block"), 32),
|
||||
},
|
||||
}
|
||||
sig, err := s.signBlockHeader(beaconState, block)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
block.Signature = sig.Marshal()
|
||||
|
||||
blocks = append(blocks, block)
|
||||
if rand.NewGenerator().Float64() < s.srvConfig.Params.ProposerSlashingProbab {
|
||||
log.WithField("proposerIndex", proposer).Infof("Slashable block made")
|
||||
slashableBlock := ðpb.SignedBeaconBlockHeader{
|
||||
Header: ðpb.BeaconBlockHeader{
|
||||
Slot: slot,
|
||||
ProposerIndex: types.ValidatorIndex(proposer),
|
||||
ParentRoot: bytesutil.PadTo([]byte{}, 32),
|
||||
StateRoot: bytesutil.PadTo([]byte{}, 32),
|
||||
BodyRoot: bytesutil.PadTo([]byte("bad block"), 32),
|
||||
},
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
sig, err = s.signBlockHeader(beaconState, slashableBlock)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
slashableBlock.Signature = sig.Marshal()
|
||||
|
||||
blocks = append(blocks, slashableBlock)
|
||||
slashings = append(slashings, ðpb.ProposerSlashing{
|
||||
Header_1: block,
|
||||
Header_2: slashableBlock,
|
||||
})
|
||||
}
|
||||
return blocks, slashings, nil
|
||||
}
|
||||
|
||||
func (s *Simulator) signBlockHeader(
|
||||
beaconState state.BeaconState,
|
||||
header *ethpb.SignedBeaconBlockHeader,
|
||||
) (bls.Signature, error) {
|
||||
domain, err := signing.Domain(
|
||||
beaconState.Fork(),
|
||||
0,
|
||||
params.BeaconConfig().DomainBeaconProposer,
|
||||
beaconState.GenesisValidatorRoot(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
htr, err := header.Header.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container := ðpb.SigningData{
|
||||
ObjectRoot: htr[:],
|
||||
Domain: domain,
|
||||
}
|
||||
signingRoot, err := container.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validatorPrivKey := s.srvConfig.PrivateKeysByValidatorIndex[header.Header.ProposerIndex]
|
||||
return validatorPrivKey.Sign(signingRoot[:]), nil
|
||||
}
|
||||
31
beacon-chain/slasher/simulator/block_generator_test.go
Normal file
31
beacon-chain/slasher/simulator/block_generator_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package simulator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
func TestGenerateBlockHeadersForSlot_Slashing(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
simParams := &Parameters{
|
||||
AggregationPercent: 1,
|
||||
NumValidators: 64,
|
||||
ProposerSlashingProbab: 1,
|
||||
}
|
||||
srv := setupService(t, simParams)
|
||||
|
||||
slot1Blocks, _, err := srv.generateBlockHeadersForSlot(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(slot1Blocks))
|
||||
|
||||
block1Root, err := slot1Blocks[0].HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
block2Root, err := slot1Blocks[1].HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
if slot1Blocks[0].Header.ProposerIndex == slot1Blocks[1].Header.ProposerIndex && bytes.Equal(block1Root[:], block2Root[:]) {
|
||||
t.Error("Blocks received were not slashable")
|
||||
}
|
||||
}
|
||||
274
beacon-chain/slasher/simulator/simulator.go
Normal file
274
beacon-chain/slasher/simulator/simulator.go
Normal file
@@ -0,0 +1,274 @@
|
||||
package simulator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
||||
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/slasher"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("prefix", "simulator")
|
||||
|
||||
// ServiceConfig for the simulator.
|
||||
type ServiceConfig struct {
|
||||
Params *Parameters
|
||||
Database db.SlasherDatabase
|
||||
StateNotifier statefeed.Notifier
|
||||
AttestationStateFetcher blockchain.AttestationStateFetcher
|
||||
HeadStateFetcher blockchain.HeadFetcher
|
||||
StateGen stategen.StateManager
|
||||
SlashingsPool slashings.PoolManager
|
||||
PrivateKeysByValidatorIndex map[types.ValidatorIndex]bls.SecretKey
|
||||
}
|
||||
|
||||
// Parameters for a slasher simulator.
|
||||
type Parameters struct {
|
||||
SecondsPerSlot uint64
|
||||
SlotsPerEpoch types.Slot
|
||||
AggregationPercent float64
|
||||
ProposerSlashingProbab float64
|
||||
AttesterSlashingProbab float64
|
||||
NumValidators uint64
|
||||
NumEpochs uint64
|
||||
}
|
||||
|
||||
// Simulator defines a struct which can launch a slasher simulation
|
||||
// at scale using configuration parameters.
|
||||
type Simulator struct {
|
||||
ctx context.Context
|
||||
slasher *slasher.Service
|
||||
srvConfig *ServiceConfig
|
||||
indexedAttsFeed *event.Feed
|
||||
beaconBlocksFeed *event.Feed
|
||||
sentAttSlashingFeed *event.Feed
|
||||
sentBlockSlashingFeed *event.Feed
|
||||
sentProposerSlashings map[[32]byte]*ethpb.ProposerSlashing
|
||||
sentAttesterSlashings map[[32]byte]*ethpb.AttesterSlashing
|
||||
genesisTime time.Time
|
||||
}
|
||||
|
||||
// DefaultParams for launching a slasher simulator.
|
||||
func DefaultParams() *Parameters {
|
||||
return &Parameters{
|
||||
SecondsPerSlot: params.BeaconConfig().SecondsPerSlot,
|
||||
SlotsPerEpoch: 4,
|
||||
AggregationPercent: 1.0,
|
||||
ProposerSlashingProbab: 0.3,
|
||||
AttesterSlashingProbab: 0.3,
|
||||
NumValidators: params.BeaconConfig().MinGenesisActiveValidatorCount,
|
||||
NumEpochs: 4,
|
||||
}
|
||||
}
|
||||
|
||||
// New initializes a slasher simulator from a beacon database
|
||||
// and configuration parameters.
|
||||
func New(ctx context.Context, srvConfig *ServiceConfig) (*Simulator, error) {
|
||||
indexedAttsFeed := new(event.Feed)
|
||||
beaconBlocksFeed := new(event.Feed)
|
||||
sentBlockSlashingFeed := new(event.Feed)
|
||||
sentAttSlashingFeed := new(event.Feed)
|
||||
|
||||
slasherSrv, err := slasher.New(ctx, &slasher.ServiceConfig{
|
||||
IndexedAttestationsFeed: indexedAttsFeed,
|
||||
BeaconBlockHeadersFeed: beaconBlocksFeed,
|
||||
Database: srvConfig.Database,
|
||||
StateNotifier: srvConfig.StateNotifier,
|
||||
HeadStateFetcher: srvConfig.HeadStateFetcher,
|
||||
AttestationStateFetcher: srvConfig.AttestationStateFetcher,
|
||||
StateGen: srvConfig.StateGen,
|
||||
SlashingPoolInserter: srvConfig.SlashingsPool,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Simulator{
|
||||
ctx: ctx,
|
||||
slasher: slasherSrv,
|
||||
srvConfig: srvConfig,
|
||||
indexedAttsFeed: indexedAttsFeed,
|
||||
beaconBlocksFeed: beaconBlocksFeed,
|
||||
sentAttSlashingFeed: sentAttSlashingFeed,
|
||||
sentBlockSlashingFeed: sentBlockSlashingFeed,
|
||||
sentProposerSlashings: make(map[[32]byte]*ethpb.ProposerSlashing),
|
||||
sentAttesterSlashings: make(map[[32]byte]*ethpb.AttesterSlashing),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start a simulator.
|
||||
func (s *Simulator) Start() {
|
||||
log.WithFields(logrus.Fields{
|
||||
"numValidators": s.srvConfig.Params.NumValidators,
|
||||
"numEpochs": s.srvConfig.Params.NumEpochs,
|
||||
"secondsPerSlot": s.srvConfig.Params.SecondsPerSlot,
|
||||
"proposerSlashingProbab": s.srvConfig.Params.ProposerSlashingProbab,
|
||||
"attesterSlashingProbab": s.srvConfig.Params.AttesterSlashingProbab,
|
||||
}).Info("Starting slasher simulator")
|
||||
|
||||
// Override global configuration for simulation purposes.
|
||||
config := params.BeaconConfig().Copy()
|
||||
config.SecondsPerSlot = s.srvConfig.Params.SecondsPerSlot
|
||||
config.SlotsPerEpoch = s.srvConfig.Params.SlotsPerEpoch
|
||||
params.OverrideBeaconConfig(config)
|
||||
defer params.OverrideBeaconConfig(params.BeaconConfig())
|
||||
|
||||
// Start slasher in the background.
|
||||
go s.slasher.Start()
|
||||
|
||||
// Wait some time and then send a "chain started" event over a notifier
|
||||
// for slasher to pick up a genesis time.
|
||||
time.Sleep(time.Second)
|
||||
s.genesisTime = time.Now()
|
||||
s.srvConfig.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.ChainStarted,
|
||||
Data: &statefeed.ChainStartedData{StartTime: s.genesisTime},
|
||||
})
|
||||
|
||||
// We simulate blocks and attestations for N epochs.
|
||||
s.simulateBlocksAndAttestations(s.ctx)
|
||||
|
||||
// Verify the slashings we detected are the same as those the
|
||||
// simulator produced, effectively checking slasher caught all slashable offenses.
|
||||
s.verifySlashingsWereDetected(s.ctx)
|
||||
}
|
||||
|
||||
// Stop the simulator.
|
||||
func (s *Simulator) Stop() error {
|
||||
return s.slasher.Stop()
|
||||
}
|
||||
|
||||
func (s *Simulator) simulateBlocksAndAttestations(ctx context.Context) {
|
||||
// Add a small offset to producing blocks and attestations a little bit after a slot starts.
|
||||
ticker := slots.NewSlotTicker(s.genesisTime.Add(time.Millisecond*500), params.BeaconConfig().SecondsPerSlot)
|
||||
defer ticker.Done()
|
||||
for {
|
||||
select {
|
||||
case slot := <-ticker.C():
|
||||
// We only run the simulator for a specified number of epochs.
|
||||
totalEpochs := types.Epoch(s.srvConfig.Params.NumEpochs)
|
||||
if core.SlotToEpoch(slot) >= totalEpochs {
|
||||
return
|
||||
}
|
||||
|
||||
// Since processing slashings requires at least one slot, we do nothing
|
||||
// if we are a few slots from the end of the simulation.
|
||||
endSlot, err := core.StartSlot(totalEpochs)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Could not get epoch start slot")
|
||||
}
|
||||
if slot+3 > endSlot {
|
||||
continue
|
||||
}
|
||||
|
||||
blockHeaders, propSlashings, err := s.generateBlockHeadersForSlot(ctx, slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Could not generate block headers for slot")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"numBlocks": len(blockHeaders),
|
||||
"numSlashable": len(propSlashings),
|
||||
}).Infof("Producing blocks for slot %d", slot)
|
||||
for _, sl := range propSlashings {
|
||||
slashingRoot, err := sl.HashTreeRoot()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Could not hash tree root slashing")
|
||||
}
|
||||
s.sentProposerSlashings[slashingRoot] = sl
|
||||
}
|
||||
for _, bb := range blockHeaders {
|
||||
s.beaconBlocksFeed.Send(bb)
|
||||
}
|
||||
|
||||
atts, attSlashings, err := s.generateAttestationsForSlot(ctx, slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Could not generate block headers for slot")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"numAtts": len(atts),
|
||||
"numSlashable": len(propSlashings),
|
||||
}).Infof("Producing attestations for slot %d", slot)
|
||||
for _, sl := range attSlashings {
|
||||
slashingRoot, err := sl.HashTreeRoot()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Could not hash tree root slashing")
|
||||
}
|
||||
s.sentAttesterSlashings[slashingRoot] = sl
|
||||
}
|
||||
for _, aa := range atts {
|
||||
s.indexedAttsFeed.Send(aa)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Simulator) verifySlashingsWereDetected(ctx context.Context) {
|
||||
poolProposerSlashings := s.srvConfig.SlashingsPool.PendingProposerSlashings(
|
||||
ctx, nil, true, /* no limit */
|
||||
)
|
||||
poolAttesterSlashings := s.srvConfig.SlashingsPool.PendingAttesterSlashings(
|
||||
ctx, nil, true, /* no limit */
|
||||
)
|
||||
detectedProposerSlashings := make(map[[32]byte]*ethpb.ProposerSlashing)
|
||||
detectedAttesterSlashings := make(map[[32]byte]*ethpb.AttesterSlashing)
|
||||
for _, slashing := range poolProposerSlashings {
|
||||
slashingRoot, err := slashing.HashTreeRoot()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not determine slashing root")
|
||||
}
|
||||
detectedProposerSlashings[slashingRoot] = slashing
|
||||
}
|
||||
for _, slashing := range poolAttesterSlashings {
|
||||
slashingRoot, err := slashing.HashTreeRoot()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not determine slashing root")
|
||||
}
|
||||
detectedAttesterSlashings[slashingRoot] = slashing
|
||||
}
|
||||
|
||||
// Check if the sent slashings made it into the slashings pool.
|
||||
for slashingRoot, slashing := range s.sentProposerSlashings {
|
||||
if _, ok := detectedProposerSlashings[slashingRoot]; !ok {
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": slashing.Header_1.Header.Slot,
|
||||
"proposerIndex": slashing.Header_1.Header.ProposerIndex,
|
||||
}).Errorf("Did not detect simulated proposer slashing")
|
||||
continue
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": slashing.Header_1.Header.Slot,
|
||||
"proposerIndex": slashing.Header_1.Header.ProposerIndex,
|
||||
}).Info("Correctly detected simulated proposer slashing")
|
||||
}
|
||||
for slashingRoot, slashing := range s.sentAttesterSlashings {
|
||||
if _, ok := detectedAttesterSlashings[slashingRoot]; !ok {
|
||||
log.WithFields(logrus.Fields{
|
||||
"targetEpoch": slashing.Attestation_1.Data.Target.Epoch,
|
||||
"prevTargetEpoch": slashing.Attestation_2.Data.Target.Epoch,
|
||||
"sourceEpoch": slashing.Attestation_1.Data.Source.Epoch,
|
||||
"prevSourceEpoch": slashing.Attestation_2.Data.Source.Epoch,
|
||||
}).Errorf("Did not detect simulated attester slashing")
|
||||
continue
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"targetEpoch": slashing.Attestation_1.Data.Target.Epoch,
|
||||
"prevTargetEpoch": slashing.Attestation_2.Data.Target.Epoch,
|
||||
"sourceEpoch": slashing.Attestation_1.Data.Source.Epoch,
|
||||
"prevSourceEpoch": slashing.Attestation_2.Data.Source.Epoch,
|
||||
}).Info("Correctly detected simulated attester slashing")
|
||||
}
|
||||
}
|
||||
48
beacon-chain/slasher/simulator/simulator_test.go
Normal file
48
beacon-chain/slasher/simulator/simulator_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package simulator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
||||
dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
)
|
||||
|
||||
func setupService(t *testing.T, params *Parameters) *Simulator {
|
||||
slasherDB := dbtest.SetupSlasherDB(t)
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
|
||||
// We setup validators in the beacon state along with their
|
||||
// private keys used to generate valid signatures in generated objects.
|
||||
validators := make([]*ethpb.Validator, params.NumValidators)
|
||||
privKeys := make(map[types.ValidatorIndex]bls.SecretKey)
|
||||
for valIdx := range validators {
|
||||
privKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeys[types.ValidatorIndex(valIdx)] = privKey
|
||||
validators[valIdx] = ðpb.Validator{
|
||||
PublicKey: privKey.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
}
|
||||
}
|
||||
err = beaconState.SetValidators(validators)
|
||||
require.NoError(t, err)
|
||||
|
||||
gen := stategen.NewMockService()
|
||||
gen.AddStateForRoot(beaconState, [32]byte{})
|
||||
return &Simulator{
|
||||
srvConfig: &ServiceConfig{
|
||||
Params: params,
|
||||
Database: slasherDB,
|
||||
AttestationStateFetcher: &mock.ChainService{State: beaconState},
|
||||
PrivateKeysByValidatorIndex: privKeys,
|
||||
StateGen: gen,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ go_library(
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//testing/endtoend:__subpackages__",
|
||||
"//testing/fuzz:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
|
||||
transition "github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db/filters"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
|
||||
@@ -52,6 +52,7 @@ go_library(
|
||||
deps = [
|
||||
"//async:go_default_library",
|
||||
"//async/abool:go_default_library",
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core:go_default_library",
|
||||
@@ -89,6 +90,7 @@ go_library(
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"//network/forks:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/metadata:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
|
||||
@@ -6,9 +6,6 @@ package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
@@ -17,6 +14,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/async"
|
||||
"github.com/prysmaticlabs/prysm/async/abool"
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
||||
@@ -37,6 +35,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/runtime"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/time"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ runtime.Service = (*Service)(nil)
|
||||
@@ -68,18 +68,21 @@ type validationFn func(ctx context.Context) (pubsub.ValidationResult, error)
|
||||
|
||||
// Config to set up the regular sync service.
|
||||
type Config struct {
|
||||
P2P p2p.P2P
|
||||
DB db.NoHeadAccessDatabase
|
||||
AttPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SlashingPool slashings.PoolManager
|
||||
SyncCommsPool synccommittee.Pool
|
||||
Chain blockchainService
|
||||
InitialSync Checker
|
||||
StateNotifier statefeed.Notifier
|
||||
BlockNotifier blockfeed.Notifier
|
||||
OperationNotifier operation.Notifier
|
||||
StateGen *stategen.State
|
||||
AttestationNotifier operation.Notifier
|
||||
P2P p2p.P2P
|
||||
DB db.NoHeadAccessDatabase
|
||||
AttPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SlashingPool slashings.PoolManager
|
||||
SyncCommsPool synccommittee.Pool
|
||||
Chain blockchainService
|
||||
InitialSync Checker
|
||||
StateNotifier statefeed.Notifier
|
||||
BlockNotifier blockfeed.Notifier
|
||||
OperationNotifier operation.Notifier
|
||||
StateGen *stategen.State
|
||||
SlasherAttestationsFeed *event.Feed
|
||||
SlasherBlockHeadersFeed *event.Feed
|
||||
}
|
||||
|
||||
// This defines the interface for interacting with block chain service
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
func TestBeaconAggregateProofSubscriber_CanSaveAggregatedAttestation(t *testing.T) {
|
||||
r := &Service{
|
||||
cfg: &Config{
|
||||
AttPool: attestations.NewPool(),
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttPool: attestations.NewPool(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenUnAggregatedAttestationCache: lruwrpr.New(10),
|
||||
}
|
||||
@@ -39,8 +39,8 @@ func TestBeaconAggregateProofSubscriber_CanSaveAggregatedAttestation(t *testing.
|
||||
func TestBeaconAggregateProofSubscriber_CanSaveUnaggregatedAttestation(t *testing.T) {
|
||||
r := &Service{
|
||||
cfg: &Config{
|
||||
AttPool: attestations.NewPool(),
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttPool: attestations.NewPool(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenUnAggregatedAttestationCache: lruwrpr.New(10),
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (s *Service) validateAggregateAndProof(ctx context.Context, pid peer.ID, ms
|
||||
|
||||
// Broadcast the aggregated attestation on a feed to notify other services in the beacon node
|
||||
// of a received aggregated attestation.
|
||||
s.cfg.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
s.cfg.AttestationNotifier.OperationFeed().Send(&feed.Event{
|
||||
Type: operation.AggregatedAttReceived,
|
||||
Data: &operation.AggregatedAttReceivedData{
|
||||
Attestation: m.Message,
|
||||
|
||||
@@ -189,8 +189,8 @@ func TestValidateAggregateAndProof_NotWithinSlotRange(t *testing.T) {
|
||||
Genesis: time.Now(),
|
||||
State: beaconState,
|
||||
},
|
||||
AttPool: attestations.NewPool(),
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttPool: attestations.NewPool(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenAggregatedAttestationCache: lruwrpr.New(10),
|
||||
}
|
||||
@@ -271,7 +271,7 @@ func TestValidateAggregateAndProof_ExistedInPool(t *testing.T) {
|
||||
InitialSync: &mockSync.Sync{IsSyncing: false},
|
||||
Chain: &mock.ChainService{Genesis: time.Now(),
|
||||
State: beaconState},
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenAggregatedAttestationCache: lruwrpr.New(10),
|
||||
blkRootToPendingAtts: make(map[[32]byte][]*ethpb.SignedAggregateAttestationAndProof),
|
||||
@@ -364,8 +364,8 @@ func TestValidateAggregateAndProof_CanValidate(t *testing.T) {
|
||||
Epoch: 0,
|
||||
Root: att.Data.BeaconBlockRoot,
|
||||
}},
|
||||
AttPool: attestations.NewPool(),
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttPool: attestations.NewPool(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenAggregatedAttestationCache: lruwrpr.New(10),
|
||||
}
|
||||
@@ -459,8 +459,8 @@ func TestVerifyIndexInCommittee_SeenAggregatorEpoch(t *testing.T) {
|
||||
Root: signedAggregateAndProof.Message.Aggregate.Data.BeaconBlockRoot,
|
||||
}},
|
||||
|
||||
AttPool: attestations.NewPool(),
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttPool: attestations.NewPool(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenAggregatedAttestationCache: lruwrpr.New(10),
|
||||
}
|
||||
@@ -570,8 +570,8 @@ func TestValidateAggregateAndProof_BadBlock(t *testing.T) {
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
}},
|
||||
AttPool: attestations.NewPool(),
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttPool: attestations.NewPool(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenAggregatedAttestationCache: lruwrpr.New(10),
|
||||
}
|
||||
@@ -661,8 +661,8 @@ func TestValidateAggregateAndProof_RejectWhenAttEpochDoesntEqualTargetEpoch(t *t
|
||||
Epoch: 0,
|
||||
Root: att.Data.BeaconBlockRoot,
|
||||
}},
|
||||
AttPool: attestations.NewPool(),
|
||||
OperationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
AttPool: attestations.NewPool(),
|
||||
AttestationNotifier: (&mock.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
seenAggregatedAttestationCache: lruwrpr.New(10),
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/monitoring/tracing"
|
||||
eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/attestation"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
@@ -63,7 +64,7 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p
|
||||
|
||||
// Broadcast the unaggregated attestation on a feed to notify other services in the beacon node
|
||||
// of a received unaggregated attestation.
|
||||
s.cfg.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
s.cfg.AttestationNotifier.OperationFeed().Send(&feed.Event{
|
||||
Type: operation.UnaggregatedAttReceived,
|
||||
Data: &operation.UnAggregatedAttReceivedData{
|
||||
Attestation: att,
|
||||
@@ -81,6 +82,32 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
|
||||
if features.Get().EnableSlasher {
|
||||
// Feed the indexed attestation to slasher if enabled. This action
|
||||
// is done in the background to avoid adding more load to this critical code path.
|
||||
go func() {
|
||||
preState, err := s.cfg.Chain.AttestationTargetState(ctx, att.Data.Target)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not retrieve pre state")
|
||||
tracing.AnnotateError(span, err)
|
||||
return
|
||||
}
|
||||
committee, err := helpers.BeaconCommitteeFromState(ctx, preState, att.Data.Slot, att.Data.CommitteeIndex)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get attestation committee")
|
||||
tracing.AnnotateError(span, err)
|
||||
return
|
||||
}
|
||||
indexedAtt, err := attestation.ConvertToIndexed(ctx, att, committee)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not convert to indexed attestation")
|
||||
traceutil.AnnotateError(span, err)
|
||||
return
|
||||
}
|
||||
s.cfg.SlasherAttestationsFeed.Send(indexedAtt)
|
||||
}()
|
||||
}
|
||||
|
||||
// Verify this the first attestation received for the participating validator for the slot.
|
||||
if s.hasSeenCommitteeIndicesSlot(att.Data.Slot, att.Data.CommitteeIndex, att.AggregationBits) {
|
||||
return pubsub.ValidationIgnore, nil
|
||||
|
||||
@@ -38,11 +38,11 @@ func TestService_validateCommitteeIndexBeaconAttestation(t *testing.T) {
|
||||
|
||||
s := &Service{
|
||||
cfg: &Config{
|
||||
InitialSync: &mockSync.Sync{IsSyncing: false},
|
||||
P2P: p,
|
||||
DB: db,
|
||||
Chain: chain,
|
||||
OperationNotifier: (&mockChain.ChainService{}).OperationNotifier(),
|
||||
InitialSync: &mockSync.Sync{IsSyncing: false},
|
||||
P2P: p,
|
||||
DB: db,
|
||||
Chain: chain,
|
||||
AttestationNotifier: (&mockChain.ChainService{}).OperationNotifier(),
|
||||
},
|
||||
blkRootToPendingAtts: make(map[[32]byte][]*ethpb.SignedAggregateAttestationAndProof),
|
||||
seenUnAggregatedAttestationCache: lruwrpr.New(10),
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/monitoring/tracing"
|
||||
@@ -70,6 +71,18 @@ func (s *Service) validateBeaconBlockPubSub(ctx context.Context, pid peer.ID, ms
|
||||
},
|
||||
})
|
||||
|
||||
if features.Get().EnableSlasher {
|
||||
// Feed the block header to slasher if enabled. This action
|
||||
// is done in the background to avoid adding more load to this critical code path.
|
||||
go func() {
|
||||
blockHeader, err := block.SignedBeaconBlockHeaderFromBlockInterface(blk)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("blockSlot", blk.Block().Slot()).Warn("Could not extract block header")
|
||||
}
|
||||
s.cfg.SlasherBlockHeadersFeed.Send(blockHeader)
|
||||
}()
|
||||
}
|
||||
|
||||
// Verify the block is the first block received for the proposer for the slot.
|
||||
if s.hasSeenBlockIndexSlot(blk.Block().Slot(), blk.Block().ProposerIndex()) {
|
||||
return pubsub.ValidationIgnore, nil
|
||||
@@ -149,6 +162,7 @@ func (s *Service) validateBeaconBlockPubSub(ctx context.Context, pid peer.ID, ms
|
||||
if err := s.validateBeaconBlock(ctx, blk, blockRoot); err != nil {
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
|
||||
// Record attribute of valid block.
|
||||
span.AddAttributes(trace.Int64Attribute("slotInEpoch", int64(blk.Block().Slot()%params.BeaconConfig().SlotsPerEpoch)))
|
||||
msg.ValidatorData = blk.Proto() // Used in downstream subscriber
|
||||
|
||||
Reference in New Issue
Block a user