prysm-pulse/beacon-chain/operations/slashings/service.go
Preston Van Loon fd80f73286
Improve make() capacity allocations (#6517)
* attestationutil.AttestingIndices: Minor improvement on indices array allocation
* progress
* more progress on makes
* progress
* more progress
* Merge branch 'master' of github.com:prysmaticlabs/prysm into memory1
* gaz
* fmt and imports
* Merge branch 'master' into memory2
* Min()
* remove spaces
* Merge branch 'master' of github.com:prysmaticlabs/prysm into memory2
* revert beacon-chain/operations/attestations/kv/block.go
* partially revert beacon-chain/operations/attestations/kv/aggregated.go
* Merge branch 'master' into memory2
* Merge branch 'master' into memory2
2020-07-09 15:50:58 +00:00

272 lines
11 KiB
Go

package slashings
import (
"context"
"fmt"
"sort"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
beaconstate "github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/shared/mathutil"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/sliceutil"
"go.opencensus.io/trace"
)
// NewPool returns an initialized attester slashing and proposer slashing pool.
func NewPool() *Pool {
return &Pool{
pendingProposerSlashing: make([]*ethpb.ProposerSlashing, 0),
pendingAttesterSlashing: make([]*PendingAttesterSlashing, 0),
included: make(map[uint64]bool),
}
}
// PendingAttesterSlashings returns attester slashings that are able to be included into a block.
// This method will not return more than the block enforced MaxAttesterSlashings.
func (p *Pool) PendingAttesterSlashings(ctx context.Context, state *beaconstate.BeaconState) []*ethpb.AttesterSlashing {
p.lock.RLock()
defer p.lock.RUnlock()
ctx, span := trace.StartSpan(ctx, "operations.PendingAttesterSlashing")
defer span.End()
// Update prom metric.
numPendingAttesterSlashings.Set(float64(len(p.pendingAttesterSlashing)))
included := make(map[uint64]bool)
// Allocate pending slice with a capacity of min(len(p.pendingAttesterSlashing), maxAttesterSlashings)
// since the array cannot exceed the max and is typically less than the max value.
pending := make([]*ethpb.AttesterSlashing, 0, mathutil.Min(uint64(len(p.pendingAttesterSlashing)), params.BeaconConfig().MaxAttesterSlashings))
for i := 0; i < len(p.pendingAttesterSlashing); i++ {
slashing := p.pendingAttesterSlashing[i]
if uint64(len(pending)) >= params.BeaconConfig().MaxAttesterSlashings {
break
}
valid, err := p.validatorSlashingPreconditionCheck(state, slashing.validatorToSlash)
if err != nil {
log.WithError(err).Error("could not validate attester slashing")
continue
}
if included[slashing.validatorToSlash] || !valid {
p.pendingAttesterSlashing = append(p.pendingAttesterSlashing[:i], p.pendingAttesterSlashing[i+1:]...)
i--
continue
}
attSlashing := slashing.attesterSlashing
slashedVal := sliceutil.IntersectionUint64(attSlashing.Attestation_1.AttestingIndices, attSlashing.Attestation_2.AttestingIndices)
for _, idx := range slashedVal {
included[idx] = true
}
pending = append(pending, attSlashing)
}
return pending
}
// PendingProposerSlashings returns proposer slashings that are able to be included into a block.
// This method will not return more than the block enforced MaxProposerSlashings.
func (p *Pool) PendingProposerSlashings(ctx context.Context, state *beaconstate.BeaconState) []*ethpb.ProposerSlashing {
p.lock.RLock()
defer p.lock.RUnlock()
ctx, span := trace.StartSpan(ctx, "operations.PendingProposerSlashing")
defer span.End()
// Update prom metric.
numPendingProposerSlashings.Set(float64(len(p.pendingProposerSlashing)))
// Allocate pending slice with a capacity of min(len(p.pendingProposerSlashing), maxProposerSlashings)
// since the array cannot exceed the max and is typically less than the max value.
pending := make([]*ethpb.ProposerSlashing, 0, mathutil.Min(uint64(len(p.pendingProposerSlashing)), params.BeaconConfig().MaxProposerSlashings))
for i := 0; i < len(p.pendingProposerSlashing); i++ {
slashing := p.pendingProposerSlashing[i]
if uint64(len(pending)) >= params.BeaconConfig().MaxProposerSlashings {
break
}
valid, err := p.validatorSlashingPreconditionCheck(state, slashing.Header_1.Header.ProposerIndex)
if err != nil {
log.WithError(err).Error("could not validate proposer slashing")
continue
}
if !valid {
p.pendingProposerSlashing = append(p.pendingProposerSlashing[:i], p.pendingProposerSlashing[i+1:]...)
i--
continue
}
pending = append(pending, slashing)
}
return pending
}
// InsertAttesterSlashing into the pool. This method is a no-op if the attester slashing already exists in the pool,
// has been included into a block recently, or the validator is already exited.
func (p *Pool) InsertAttesterSlashing(
ctx context.Context,
state *beaconstate.BeaconState,
slashing *ethpb.AttesterSlashing,
) error {
p.lock.Lock()
defer p.lock.Unlock()
ctx, span := trace.StartSpan(ctx, "operations.InsertAttesterSlashing")
defer span.End()
if err := blocks.VerifyAttesterSlashing(ctx, state, slashing); err != nil {
numPendingAttesterSlashingFailedSigVerify.Inc()
return errors.Wrap(err, "could not verify attester slashing")
}
slashedVal := sliceutil.IntersectionUint64(slashing.Attestation_1.AttestingIndices, slashing.Attestation_2.AttestingIndices)
cantSlash := make([]uint64, 0, len(slashedVal))
for _, val := range slashedVal {
// Has this validator index been included recently?
ok, err := p.validatorSlashingPreconditionCheck(state, val)
if err != nil {
return err
}
// If the validator has already exited, has already been slashed, or if its index
// has been recently included in the pool of slashings, skip including this indice.
if !ok {
attesterSlashingReattempts.Inc()
cantSlash = append(cantSlash, val)
continue
}
// Check if the validator already exists in the list of slashings.
// Use binary search to find the answer.
found := sort.Search(len(p.pendingAttesterSlashing), func(i int) bool {
return p.pendingAttesterSlashing[i].validatorToSlash >= val
})
if found != len(p.pendingAttesterSlashing) && p.pendingAttesterSlashing[found].validatorToSlash == val {
attesterSlashingReattempts.Inc()
cantSlash = append(cantSlash, val)
continue
}
pendingSlashing := &PendingAttesterSlashing{
attesterSlashing: slashing,
validatorToSlash: val,
}
// Insert into pending list and sort again.
p.pendingAttesterSlashing = append(p.pendingAttesterSlashing, pendingSlashing)
sort.Slice(p.pendingAttesterSlashing, func(i, j int) bool {
return p.pendingAttesterSlashing[i].validatorToSlash < p.pendingAttesterSlashing[j].validatorToSlash
})
numPendingAttesterSlashings.Set(float64(len(p.pendingAttesterSlashing)))
}
if len(cantSlash) == len(slashedVal) {
return fmt.Errorf("could not slash any of %d validators in submitted slashing", len(slashedVal))
}
return nil
}
// InsertProposerSlashing into the pool. This method is a no-op if the pending slashing already exists,
// has been included recently, the validator is already exited, or the validator was already slashed.
func (p *Pool) InsertProposerSlashing(
ctx context.Context,
state *beaconstate.BeaconState,
slashing *ethpb.ProposerSlashing,
) error {
p.lock.Lock()
defer p.lock.Unlock()
ctx, span := trace.StartSpan(ctx, "operations.InsertProposerSlashing")
defer span.End()
if err := blocks.VerifyProposerSlashing(state, slashing); err != nil {
numPendingProposerSlashingFailedSigVerify.Inc()
return errors.Wrap(err, "could not verify proposer slashing")
}
idx := slashing.Header_1.Header.ProposerIndex
ok, err := p.validatorSlashingPreconditionCheck(state, idx)
if err != nil {
return err
}
// If the validator has already exited, has already been slashed, or if its index
// has been recently included in the pool of slashings, do not process this new
// slashing.
if !ok {
proposerSlashingReattempts.Inc()
return fmt.Errorf("validator at index %d cannot be slashed", idx)
}
// Check if the validator already exists in the list of slashings.
// Use binary search to find the answer.
found := sort.Search(len(p.pendingProposerSlashing), func(i int) bool {
return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex >= slashing.Header_1.Header.ProposerIndex
})
if found != len(p.pendingProposerSlashing) && p.pendingProposerSlashing[found].Header_1.Header.ProposerIndex ==
slashing.Header_1.Header.ProposerIndex {
return errors.New("slashing object already exists in pending proposer slashings")
}
// Insert into pending list and sort again.
p.pendingProposerSlashing = append(p.pendingProposerSlashing, slashing)
sort.Slice(p.pendingProposerSlashing, func(i, j int) bool {
return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex < p.pendingProposerSlashing[j].Header_1.Header.ProposerIndex
})
numPendingProposerSlashings.Set(float64(len(p.pendingProposerSlashing)))
return nil
}
// MarkIncludedAttesterSlashing is used when an attester slashing has been included in a beacon block.
// Every block seen by this node that contains proposer slashings should call this method to include
// the proposer slashings.
func (p *Pool) MarkIncludedAttesterSlashing(as *ethpb.AttesterSlashing) {
p.lock.Lock()
defer p.lock.Unlock()
slashedVal := sliceutil.IntersectionUint64(as.Attestation_1.AttestingIndices, as.Attestation_2.AttestingIndices)
for _, val := range slashedVal {
i := sort.Search(len(p.pendingAttesterSlashing), func(i int) bool {
return p.pendingAttesterSlashing[i].validatorToSlash >= val
})
if i != len(p.pendingAttesterSlashing) && p.pendingAttesterSlashing[i].validatorToSlash == val {
p.pendingAttesterSlashing = append(p.pendingAttesterSlashing[:i], p.pendingAttesterSlashing[i+1:]...)
}
p.included[val] = true
numAttesterSlashingsIncluded.Inc()
}
}
// MarkIncludedProposerSlashing is used when an proposer slashing has been included in a beacon block.
// Every block seen by this node that contains proposer slashings should call this method to include
// the proposer slashings.
func (p *Pool) MarkIncludedProposerSlashing(ps *ethpb.ProposerSlashing) {
p.lock.Lock()
defer p.lock.Unlock()
i := sort.Search(len(p.pendingProposerSlashing), func(i int) bool {
return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex >= ps.Header_1.Header.ProposerIndex
})
if i != len(p.pendingProposerSlashing) && p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex == ps.Header_1.Header.ProposerIndex {
p.pendingProposerSlashing = append(p.pendingProposerSlashing[:i], p.pendingProposerSlashing[i+1:]...)
}
p.included[ps.Header_1.Header.ProposerIndex] = true
numProposerSlashingsIncluded.Inc()
}
// this function checks a few items about a validator before proceeding with inserting
// a proposer/attester slashing into the pool. First, it checks if the validator
// has been recently included in the pool, then it checks if the validator is slashable.
func (p *Pool) validatorSlashingPreconditionCheck(
state *beaconstate.BeaconState,
valIdx uint64,
) (bool, error) {
// Check if the validator index has been included recently.
if p.included[valIdx] {
return false, nil
}
validator, err := state.ValidatorAtIndexReadOnly(valIdx)
if err != nil {
return false, err
}
// Checking if the validator is slashable.
if !helpers.IsSlashableValidatorUsingTrie(validator, helpers.CurrentEpoch(state)) {
return false, nil
}
return true, nil
}