package rpc import ( "context" "github.com/pkg/errors" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" slashpb "github.com/prysmaticlabs/prysm/proto/slashing" "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" "github.com/prysmaticlabs/prysm/slasher/db" "github.com/prysmaticlabs/prysm/slasher/detection" "go.opencensus.io/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // Server defines a server implementation of the gRPC Slasher service, // providing RPC endpoints for retrieving slashing proofs for malicious validators. type Server struct { ctx context.Context detector *detection.Service slasherDB db.Database beaconClient *beaconclient.Service } // IsSlashableAttestation returns an attester slashing if the attestation submitted // is a slashable vote. func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.IndexedAttestation) (*slashpb.AttesterSlashingResponse, error) { ctx, span := trace.StartSpan(ctx, "detection.IsSlashableAttestation") defer span.End() 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") } err := attestationutil.IsValidAttestationIndices(ctx, req) if err != nil { return nil, err } gvr, err := ss.beaconClient.GenesisValidatorsRoot(ctx) 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 } indices := req.AttestingIndices pkMap, err := ss.beaconClient.FindOrGetPublicKeys(ctx, indices) if err != nil { return nil, err } pubkeys := []bls.PublicKey{} for _, pkBytes := range pkMap { pk, err := bls.PublicKeyFromBytes(pkBytes[:]) 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 { 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) } slashings, err := ss.detector.DetectAttesterSlashings(ctx, req) if err != nil { return nil, status.Errorf(codes.Internal, "could not detect attester slashings for attestation: %v: %v", req, err) } if len(slashings) < 1 { if err := ss.slasherDB.SaveIndexedAttestation(ctx, req); err != nil { log.WithError(err).Error("Could not save indexed attestation") return nil, status.Errorf(codes.Internal, "could not save indexed attestation: %v: %v", req, err) } if err := ss.detector.UpdateSpans(ctx, req); err != nil { log.WithError(err).Error("could not update spans") } } return &slashpb.AttesterSlashingResponse{ AttesterSlashing: slashings, }, nil } // IsSlashableBlock returns an proposer slashing if the block submitted // is a double proposal. func (ss *Server) IsSlashableBlock(ctx context.Context, req *ethpb.SignedBeaconBlockHeader) (*slashpb.ProposerSlashingResponse, error) { ctx, span := trace.StartSpan(ctx, "detection.IsSlashableBlock") defer span.End() 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") } gvr, err := ss.beaconClient.GenesisValidatorsRoot(ctx) 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 } pkMap, err := ss.beaconClient.FindOrGetPublicKeys(ctx, []uint64{req.Header.ProposerIndex}) if err := helpers.VerifyBlockHeaderSigningRoot( req.Header, pkMap[req.Header.ProposerIndex], req.Signature, domain); err != nil { return nil, err } slashing, err := ss.detector.DetectDoubleProposals(ctx, req) if err != nil { return nil, status.Errorf(codes.Internal, "could not detect proposer slashing for block: %v: %v", req, err) } psr := &slashpb.ProposerSlashingResponse{} if slashing != nil { psr = &slashpb.ProposerSlashingResponse{ ProposerSlashing: []*ethpb.ProposerSlashing{slashing}, } } return psr, nil } // IsSlashableAttestationNoUpdate returns true if the attestation submitted // is a slashable vote (no db update is being done). func (ss *Server) IsSlashableAttestationNoUpdate(ctx context.Context, req *ethpb.IndexedAttestation) (*slashpb.Slashable, error) { sl := &slashpb.Slashable{} slashings, err := ss.detector.DetectAttesterSlashings(ctx, req) 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). func (ss *Server) IsSlashableBlockNoUpdate(ctx context.Context, req *ethpb.BeaconBlockHeader) (*slashpb.Slashable, error) { sl := &slashpb.Slashable{} slash, err := ss.detector.DetectDoubleProposeNoUpdate(ctx, req) 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 }