2019-10-17 03:12:26 +00:00
|
|
|
package rpc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-08-04 19:50:23 +00:00
|
|
|
"sync"
|
2019-10-17 03:12:26 +00:00
|
|
|
|
2019-11-20 05:14:50 +00:00
|
|
|
"github.com/pkg/errors"
|
2021-02-23 00:14:50 +00:00
|
|
|
types "github.com/prysmaticlabs/eth2-types"
|
2020-05-05 05:15:32 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
2021-07-21 21:34:07 +00:00
|
|
|
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
2021-07-28 21:23:44 +00:00
|
|
|
slashpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
2020-05-05 05:15:32 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/attestationutil"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/bls"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/p2putils"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
|
|
"github.com/prysmaticlabs/prysm/slasher/beaconclient"
|
2020-03-27 00:09:14 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/slasher/db"
|
2020-03-24 18:30:21 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/slasher/detection"
|
2020-08-20 07:31:16 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2020-03-26 18:31:20 +00:00
|
|
|
"go.opencensus.io/trace"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
2019-10-17 03:12:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Server defines a server implementation of the gRPC Slasher service,
|
|
|
|
// providing RPC endpoints for retrieving slashing proofs for malicious validators.
|
|
|
|
type Server struct {
|
2020-08-04 19:50:23 +00:00
|
|
|
ctx context.Context
|
|
|
|
detector *detection.Service
|
|
|
|
slasherDB db.Database
|
|
|
|
beaconClient *beaconclient.Service
|
|
|
|
attestationLock sync.Mutex
|
|
|
|
proposeLock sync.Mutex
|
2019-10-17 03:12:26 +00:00
|
|
|
}
|
|
|
|
|
2020-11-22 08:51:20 +00:00
|
|
|
// HighestAttestations returns the highest observed attestation source and epoch for a given validator id.
|
2021-01-20 14:39:07 +00:00
|
|
|
func (s *Server) HighestAttestations(ctx context.Context, req *slashpb.HighestAttestationRequest) (*slashpb.HighestAttestationResponse, error) {
|
2020-11-22 08:51:20 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "history.HighestAttestations")
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
ret := make([]*slashpb.HighestAttestation, 0)
|
2021-07-23 18:01:01 +00:00
|
|
|
for _, id := range req.ValidatorIndices {
|
2020-11-22 08:51:20 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
|
|
|
|
2021-01-20 14:39:07 +00:00
|
|
|
res, err := s.slasherDB.HighestAttestation(ctx, id)
|
2020-11-22 08:51:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if res != nil {
|
|
|
|
ret = append(ret, &slashpb.HighestAttestation{
|
2021-07-23 18:01:01 +00:00
|
|
|
ValidatorIndex: res.ValidatorIndex,
|
2020-11-22 08:51:20 +00:00
|
|
|
HighestTargetEpoch: res.HighestTargetEpoch,
|
|
|
|
HighestSourceEpoch: res.HighestSourceEpoch,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &slashpb.HighestAttestationResponse{
|
|
|
|
Attestations: ret,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-10-17 03:12:26 +00:00
|
|
|
// IsSlashableAttestation returns an attester slashing if the attestation submitted
|
|
|
|
// is a slashable vote.
|
2021-01-20 14:39:07 +00:00
|
|
|
func (s *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.IndexedAttestation) (*slashpb.AttesterSlashingResponse, error) {
|
2020-03-26 18:31:20 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "detection.IsSlashableAttestation")
|
|
|
|
defer span.End()
|
2020-05-08 05:51:56 +00:00
|
|
|
|
2020-08-20 07:31:16 +00:00
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"slot": req.Data.Slot,
|
|
|
|
"indices": req.AttestingIndices,
|
|
|
|
}).Debug("Received attestation via RPC")
|
2020-05-08 05:51:56 +00:00
|
|
|
if req == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil request provided")
|
|
|
|
}
|
|
|
|
if req.Data == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil request data provided")
|
|
|
|
}
|
|
|
|
if req.Data.Target == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil request data target provided")
|
|
|
|
}
|
|
|
|
if req.Data.Source == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil request data source provided")
|
|
|
|
}
|
|
|
|
if req.Signature == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil signature provided")
|
|
|
|
}
|
|
|
|
|
2020-05-05 05:15:32 +00:00
|
|
|
err := attestationutil.IsValidAttestationIndices(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-01-20 14:39:07 +00:00
|
|
|
gvr, err := s.beaconClient.GenesisValidatorsRoot(ctx)
|
2020-05-05 05:15:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fork, err := p2putils.Fork(req.Data.Target.Epoch)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
domain, err := helpers.Domain(fork, req.Data.Target.Epoch, params.BeaconConfig().DomainBeaconAttester, gvr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-02-23 00:14:50 +00:00
|
|
|
indices := make([]types.ValidatorIndex, len(req.AttestingIndices))
|
|
|
|
for i, index := range req.AttestingIndices {
|
|
|
|
indices[i] = types.ValidatorIndex(index)
|
|
|
|
}
|
2021-01-20 14:39:07 +00:00
|
|
|
pkMap, err := s.beaconClient.FindOrGetPublicKeys(ctx, indices)
|
2020-05-05 05:15:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-12 08:11:05 +00:00
|
|
|
var pubkeys []bls.PublicKey
|
2020-05-05 05:15:32 +00:00
|
|
|
for _, pkBytes := range pkMap {
|
2020-09-23 16:14:34 +00:00
|
|
|
pk, err := bls.PublicKeyFromBytes(pkBytes)
|
2020-05-05 05:15:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not deserialize validator public key")
|
|
|
|
}
|
|
|
|
pubkeys = append(pubkeys, pk)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = attestationutil.VerifyIndexedAttestationSig(ctx, req, pubkeys, domain)
|
|
|
|
if err != nil {
|
2020-06-11 18:50:12 +00:00
|
|
|
log.WithError(err).Error("failed to verify indexed attestation signature")
|
|
|
|
return nil, status.Errorf(codes.Internal, "could not verify indexed attestation signature: %v: %v", req, err)
|
2020-05-05 05:15:32 +00:00
|
|
|
}
|
2020-08-04 19:50:23 +00:00
|
|
|
|
2021-01-20 14:39:07 +00:00
|
|
|
s.attestationLock.Lock()
|
|
|
|
defer s.attestationLock.Unlock()
|
2020-08-04 19:50:23 +00:00
|
|
|
|
2021-01-20 14:39:07 +00:00
|
|
|
slashings, err := s.detector.DetectAttesterSlashings(ctx, req)
|
2020-03-26 18:31:20 +00:00
|
|
|
if err != nil {
|
2020-06-11 18:50:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "could not detect attester slashings for attestation: %v: %v", req, err)
|
2020-03-26 18:31:20 +00:00
|
|
|
}
|
|
|
|
if len(slashings) < 1 {
|
2021-01-20 14:39:07 +00:00
|
|
|
if err := s.slasherDB.SaveIndexedAttestation(ctx, req); err != nil {
|
2020-05-20 15:23:22 +00:00
|
|
|
log.WithError(err).Error("Could not save indexed attestation")
|
2020-06-11 18:50:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "could not save indexed attestation: %v: %v", req, err)
|
2020-05-20 15:23:22 +00:00
|
|
|
}
|
2021-01-20 14:39:07 +00:00
|
|
|
if err := s.detector.UpdateSpans(ctx, req); err != nil {
|
2020-06-11 18:50:12 +00:00
|
|
|
log.WithError(err).Error("could not update spans")
|
2020-09-18 10:09:29 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "failed to update spans: %v: %v", req, err)
|
2020-03-26 18:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return &slashpb.AttesterSlashingResponse{
|
2021-07-23 18:01:01 +00:00
|
|
|
AttesterSlashings: slashings,
|
2020-03-26 18:31:20 +00:00
|
|
|
}, nil
|
2019-10-17 03:12:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-03 22:09:35 +00:00
|
|
|
// IsSlashableBlock returns an proposer slashing if the block submitted
|
|
|
|
// is a double proposal.
|
2021-01-20 14:39:07 +00:00
|
|
|
func (s *Server) IsSlashableBlock(ctx context.Context, req *ethpb.SignedBeaconBlockHeader) (*slashpb.ProposerSlashingResponse, error) {
|
2020-05-08 05:51:56 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "detection.IsSlashableBlock")
|
|
|
|
defer span.End()
|
|
|
|
|
2020-08-20 07:31:16 +00:00
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"slot": req.Header.Slot,
|
|
|
|
"proposer_index": req.Header.ProposerIndex,
|
|
|
|
}).Info("Received block via RPC")
|
2020-05-08 05:51:56 +00:00
|
|
|
if req == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil request provided")
|
|
|
|
}
|
|
|
|
if req.Header == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil header provided")
|
|
|
|
|
|
|
|
}
|
|
|
|
if req.Signature == nil {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "nil signature provided")
|
|
|
|
}
|
2021-01-20 14:39:07 +00:00
|
|
|
gvr, err := s.beaconClient.GenesisValidatorsRoot(ctx)
|
2020-05-08 05:51:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
blockEpoch := helpers.SlotToEpoch(req.Header.Slot)
|
|
|
|
fork, err := p2putils.Fork(blockEpoch)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
domain, err := helpers.Domain(fork, blockEpoch, params.BeaconConfig().DomainBeaconProposer, gvr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-02-23 00:14:50 +00:00
|
|
|
pkMap, err := s.beaconClient.FindOrGetPublicKeys(ctx, []types.ValidatorIndex{req.Header.ProposerIndex})
|
2020-09-11 14:53:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-08 05:51:56 +00:00
|
|
|
if err := helpers.VerifyBlockHeaderSigningRoot(
|
2020-09-11 14:53:53 +00:00
|
|
|
req.Header,
|
|
|
|
pkMap[req.Header.ProposerIndex],
|
|
|
|
req.Signature, domain); err != nil {
|
2020-05-08 05:51:56 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-04 19:50:23 +00:00
|
|
|
|
2021-01-20 14:39:07 +00:00
|
|
|
s.proposeLock.Lock()
|
|
|
|
defer s.proposeLock.Unlock()
|
2020-08-04 19:50:23 +00:00
|
|
|
|
2021-01-20 14:39:07 +00:00
|
|
|
slashing, err := s.detector.DetectDoubleProposals(ctx, req)
|
2020-05-08 05:51:56 +00:00
|
|
|
if err != nil {
|
2020-06-11 18:50:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "could not detect proposer slashing for block: %v: %v", req, err)
|
2020-05-08 05:51:56 +00:00
|
|
|
}
|
|
|
|
psr := &slashpb.ProposerSlashingResponse{}
|
|
|
|
if slashing != nil {
|
|
|
|
psr = &slashpb.ProposerSlashingResponse{
|
2021-07-23 18:01:01 +00:00
|
|
|
ProposerSlashings: []*ethpb.ProposerSlashing{slashing},
|
2020-05-08 05:51:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return psr, nil
|
|
|
|
|
2019-11-21 07:11:23 +00:00
|
|
|
}
|
2020-06-11 18:50:12 +00:00
|
|
|
|
|
|
|
// IsSlashableAttestationNoUpdate returns true if the attestation submitted
|
|
|
|
// is a slashable vote (no db update is being done).
|
2021-01-20 14:39:07 +00:00
|
|
|
func (s *Server) IsSlashableAttestationNoUpdate(ctx context.Context, req *ethpb.IndexedAttestation) (*slashpb.Slashable, error) {
|
2020-06-11 18:50:12 +00:00
|
|
|
sl := &slashpb.Slashable{}
|
2021-01-20 14:39:07 +00:00
|
|
|
slashings, err := s.detector.DetectAttesterSlashings(ctx, req)
|
2020-06-11 18:50:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return sl, status.Errorf(codes.Internal, "could not detect attester slashings for attestation: %v: %v", req, err)
|
|
|
|
}
|
|
|
|
if len(slashings) < 1 {
|
|
|
|
return sl, nil
|
|
|
|
}
|
|
|
|
sl.Slashable = true
|
|
|
|
return sl, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsSlashableBlockNoUpdate returns true if the block submitted
|
|
|
|
// is slashable (no db update is being done).
|
2021-01-20 14:39:07 +00:00
|
|
|
func (s *Server) IsSlashableBlockNoUpdate(ctx context.Context, req *ethpb.BeaconBlockHeader) (*slashpb.Slashable, error) {
|
2020-06-11 18:50:12 +00:00
|
|
|
sl := &slashpb.Slashable{}
|
2021-01-20 14:39:07 +00:00
|
|
|
slash, err := s.detector.DetectDoubleProposeNoUpdate(ctx, req)
|
2020-06-11 18:50:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return sl, status.Errorf(codes.Internal, "could not detect proposer slashing for block: %v: %v", req, err)
|
|
|
|
}
|
|
|
|
sl.Slashable = slash
|
|
|
|
return sl, nil
|
|
|
|
}
|