mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 21:57:16 +00:00
18fbdd53b9
* Rename elements for clarity * Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-renames * Fix test * Rename more functions * Cleanup * Fix logs * Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-renames * Reorganize and clean up logs * Address comments * Add comments
241 lines
7.6 KiB
Go
241 lines
7.6 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"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/hashutil"
|
|
"github.com/prysmaticlabs/prysm/slasher/db"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Server defines a server implementation of the gRPC Slasher service,
|
|
// providing RPC endpoints for retrieving slashing proofs for malicious validators.
|
|
type Server struct {
|
|
SlasherDB *db.Store
|
|
ctx context.Context
|
|
}
|
|
|
|
// 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) {
|
|
//TODO(#3133): add signature validation
|
|
if req.Data == nil {
|
|
return nil, fmt.Errorf("cant hash nil data in indexed attestation")
|
|
}
|
|
if err := ss.SlasherDB.SaveIndexedAttestation(req); err != nil {
|
|
return nil, err
|
|
}
|
|
indices := req.AttestingIndices
|
|
root, err := hashutil.HashProto(req.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attSlashingResp := &slashpb.AttesterSlashingResponse{}
|
|
attSlashings := make(chan []*ethpb.AttesterSlashing, len(indices))
|
|
errorChans := make(chan error, len(indices))
|
|
var wg sync.WaitGroup
|
|
lastIdx := int64(-1)
|
|
for _, idx := range indices {
|
|
if int64(idx) <= lastIdx {
|
|
return nil, fmt.Errorf("indexed attestation contains repeated or non sorted ids")
|
|
}
|
|
wg.Add(1)
|
|
go func(idx uint64, root [32]byte, req *ethpb.IndexedAttestation) {
|
|
atts, err := ss.SlasherDB.DoubleVotes(idx, root[:], req)
|
|
if err != nil {
|
|
errorChans <- err
|
|
wg.Done()
|
|
return
|
|
}
|
|
if atts != nil && len(atts) > 0 {
|
|
attSlashings <- atts
|
|
}
|
|
atts, err = ss.DetectSurroundVotes(ctx, idx, req)
|
|
if err != nil {
|
|
errorChans <- err
|
|
wg.Done()
|
|
return
|
|
}
|
|
if atts != nil && len(atts) > 0 {
|
|
attSlashings <- atts
|
|
}
|
|
wg.Done()
|
|
return
|
|
}(idx, root, req)
|
|
}
|
|
wg.Wait()
|
|
close(errorChans)
|
|
close(attSlashings)
|
|
for e := range errorChans {
|
|
if err != nil {
|
|
err = fmt.Errorf(err.Error() + " : " + e.Error())
|
|
continue
|
|
}
|
|
err = e
|
|
}
|
|
for atts := range attSlashings {
|
|
attSlashingResp.AttesterSlashing = append(attSlashingResp.AttesterSlashing, atts...)
|
|
}
|
|
return attSlashingResp, err
|
|
}
|
|
|
|
// UpdateSpanMaps updates and load all span maps from db.
|
|
func (ss *Server) UpdateSpanMaps(ctx context.Context, req *ethpb.IndexedAttestation) error {
|
|
indices := req.AttestingIndices
|
|
lastIdx := int64(-1)
|
|
var wg sync.WaitGroup
|
|
er := make(chan error, len(indices))
|
|
for _, idx := range indices {
|
|
if int64(idx) <= lastIdx {
|
|
er <- fmt.Errorf("indexed attestation contains repeated or non sorted ids")
|
|
}
|
|
wg.Add(1)
|
|
go func(i uint64) {
|
|
spanMap, err := ss.SlasherDB.ValidatorSpansMap(i)
|
|
if err != nil {
|
|
er <- err
|
|
wg.Done()
|
|
return
|
|
}
|
|
if req.Data == nil {
|
|
log.Trace("Got indexed attestation with no data")
|
|
wg.Done()
|
|
return
|
|
}
|
|
_, spanMap, err = ss.DetectAndUpdateMinEpochSpan(ctx, req.Data.Source.Epoch, req.Data.Target.Epoch, i, spanMap)
|
|
if err != nil {
|
|
er <- err
|
|
wg.Done()
|
|
return
|
|
}
|
|
_, spanMap, err = ss.DetectAndUpdateMaxEpochSpan(ctx, req.Data.Source.Epoch, req.Data.Target.Epoch, i, spanMap)
|
|
if err != nil {
|
|
er <- err
|
|
wg.Done()
|
|
return
|
|
}
|
|
if err := ss.SlasherDB.SaveValidatorSpansMap(i, spanMap); err != nil {
|
|
er <- err
|
|
wg.Done()
|
|
return
|
|
}
|
|
}(idx)
|
|
wg.Wait()
|
|
}
|
|
close(er)
|
|
for e := range er {
|
|
log.Errorf("Got error while trying to update span maps: %v", e)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsSlashableBlock returns a proposer slashing if the block header submitted is
|
|
// a slashable proposal.
|
|
func (ss *Server) IsSlashableBlock(ctx context.Context, psr *slashpb.ProposerSlashingRequest) (*slashpb.ProposerSlashingResponse, error) {
|
|
//TODO(#3133): add signature validation
|
|
epoch := helpers.SlotToEpoch(psr.BlockHeader.Header.Slot)
|
|
blockHeaders, err := ss.SlasherDB.BlockHeaders(epoch, psr.ValidatorIndex)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "slasher service error while trying to retrieve blocks")
|
|
}
|
|
pSlashingsResponse := &slashpb.ProposerSlashingResponse{}
|
|
presentInDb := false
|
|
for _, bh := range blockHeaders {
|
|
if proto.Equal(bh, psr.BlockHeader) {
|
|
presentInDb = true
|
|
continue
|
|
}
|
|
pSlashingsResponse.ProposerSlashing = append(pSlashingsResponse.ProposerSlashing, ðpb.ProposerSlashing{ProposerIndex: psr.ValidatorIndex, Header_1: psr.BlockHeader, Header_2: bh})
|
|
}
|
|
if len(pSlashingsResponse.ProposerSlashing) == 0 && !presentInDb {
|
|
err = ss.SlasherDB.SaveBlockHeader(epoch, psr.ValidatorIndex, psr.BlockHeader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return pSlashingsResponse, nil
|
|
}
|
|
|
|
// ProposerSlashings returns proposer slashings if slashing with the requested status are found in the db.
|
|
func (ss *Server) ProposerSlashings(ctx context.Context, st *slashpb.SlashingStatusRequest) (*slashpb.ProposerSlashingResponse, error) {
|
|
pSlashingsResponse := &slashpb.ProposerSlashingResponse{}
|
|
var err error
|
|
pSlashingsResponse.ProposerSlashing, err = ss.SlasherDB.ProposalSlashingsByStatus(db.SlashingStatus(st.Status))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pSlashingsResponse, nil
|
|
}
|
|
|
|
// AttesterSlashings returns attester slashings if slashing with the requested status are found in the db.
|
|
func (ss *Server) AttesterSlashings(ctx context.Context, st *slashpb.SlashingStatusRequest) (*slashpb.AttesterSlashingResponse, error) {
|
|
aSlashingsResponse := &slashpb.AttesterSlashingResponse{}
|
|
var err error
|
|
aSlashingsResponse.AttesterSlashing, err = ss.SlasherDB.AttesterSlashings(db.SlashingStatus(st.Status))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return aSlashingsResponse, nil
|
|
}
|
|
|
|
// DetectSurroundVotes is a method used to return the attestation that were detected
|
|
// by min max surround detection method.
|
|
func (ss *Server) DetectSurroundVotes(ctx context.Context, validatorIdx uint64, req *ethpb.IndexedAttestation) ([]*ethpb.AttesterSlashing, error) {
|
|
spanMap, err := ss.SlasherDB.ValidatorSpansMap(validatorIdx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get validator spans map")
|
|
}
|
|
minTargetEpoch, spanMap, err := ss.DetectAndUpdateMinEpochSpan(ctx, req.Data.Source.Epoch, req.Data.Target.Epoch, validatorIdx, spanMap)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to update min spans")
|
|
}
|
|
maxTargetEpoch, spanMap, err := ss.DetectAndUpdateMaxEpochSpan(ctx, req.Data.Source.Epoch, req.Data.Target.Epoch, validatorIdx, spanMap)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to update max spans")
|
|
}
|
|
if err := ss.SlasherDB.SaveValidatorSpansMap(validatorIdx, spanMap); err != nil {
|
|
return nil, errors.Wrap(err, "failed to save validator spans map")
|
|
}
|
|
|
|
var as []*ethpb.AttesterSlashing
|
|
if minTargetEpoch > 0 {
|
|
attestations, err := ss.SlasherDB.IdxAttsForTargetFromID(minTargetEpoch, validatorIdx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, ia := range attestations {
|
|
if ia.Data == nil {
|
|
continue
|
|
}
|
|
if ia.Data.Source.Epoch > req.Data.Source.Epoch && ia.Data.Target.Epoch < req.Data.Target.Epoch {
|
|
as = append(as, ðpb.AttesterSlashing{
|
|
Attestation_1: req,
|
|
Attestation_2: ia,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if maxTargetEpoch > 0 {
|
|
attestations, err := ss.SlasherDB.IdxAttsForTargetFromID(maxTargetEpoch, validatorIdx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, ia := range attestations {
|
|
if ia.Data.Source.Epoch < req.Data.Source.Epoch && ia.Data.Target.Epoch > req.Data.Target.Epoch {
|
|
as = append(as, ðpb.AttesterSlashing{
|
|
Attestation_1: req,
|
|
Attestation_2: ia,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return as, nil
|
|
}
|