package blocks import ( "context" "fmt" "github.com/pkg/errors" types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/signing" "github.com/prysmaticlabs/prysm/beacon-chain/core/time" "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/config/params" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/runtime/version" "github.com/prysmaticlabs/prysm/time/slots" "google.golang.org/protobuf/proto" ) type slashValidatorFunc func(ctx context.Context, st state.BeaconState, vid types.ValidatorIndex, penaltyQuotient, proposerRewardQuotient uint64) (state.BeaconState, error) // ProcessProposerSlashings is one of the operations performed // on each processed beacon block to slash proposers based on // slashing conditions if any slashable events occurred. // // Spec pseudocode definition: // def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: // header_1 = proposer_slashing.signed_header_1.message // header_2 = proposer_slashing.signed_header_2.message // // # Verify header slots match // assert header_1.slot == header_2.slot // # Verify header proposer indices match // assert header_1.proposer_index == header_2.proposer_index // # Verify the headers are different // assert header_1 != header_2 // # Verify the proposer is slashable // proposer = state.validators[header_1.proposer_index] // assert is_slashable_validator(proposer, get_current_epoch(state)) // # Verify signatures // for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): // domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) // signing_root = compute_signing_root(signed_header.message, domain) // assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) // // slash_validator(state, header_1.proposer_index) func ProcessProposerSlashings( ctx context.Context, beaconState state.BeaconState, slashings []*ethpb.ProposerSlashing, slashFunc slashValidatorFunc, ) (state.BeaconState, error) { var err error for _, slashing := range slashings { beaconState, err = ProcessProposerSlashing(ctx, beaconState, slashing, slashFunc) if err != nil { return nil, err } } return beaconState, nil } // ProcessProposerSlashing processes individual proposer slashing. func ProcessProposerSlashing( ctx context.Context, beaconState state.BeaconState, slashing *ethpb.ProposerSlashing, slashFunc slashValidatorFunc, ) (state.BeaconState, error) { var err error if slashing == nil { return nil, errors.New("nil proposer slashings in block body") } if err = VerifyProposerSlashing(beaconState, slashing); err != nil { return nil, errors.Wrap(err, "could not verify proposer slashing") } cfg := params.BeaconConfig() var slashingQuotient uint64 switch { case beaconState.Version() == version.Phase0: slashingQuotient = cfg.MinSlashingPenaltyQuotient case beaconState.Version() == version.Altair: slashingQuotient = cfg.MinSlashingPenaltyQuotientAltair case beaconState.Version() == version.Bellatrix: slashingQuotient = cfg.MinSlashingPenaltyQuotientBellatrix default: return nil, errors.New("unknown state version") } beaconState, err = slashFunc(ctx, beaconState, slashing.Header_1.Header.ProposerIndex, slashingQuotient, cfg.ProposerRewardQuotient) if err != nil { return nil, errors.Wrapf(err, "could not slash proposer index %d", slashing.Header_1.Header.ProposerIndex) } return beaconState, nil } // VerifyProposerSlashing verifies that the data provided from slashing is valid. func VerifyProposerSlashing( 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 { return errors.New("nil header cannot be verified") } hSlot := slashing.Header_1.Header.Slot if hSlot != slashing.Header_2.Header.Slot { return fmt.Errorf("mismatched header slots, received %d == %d", slashing.Header_1.Header.Slot, slashing.Header_2.Header.Slot) } pIdx := slashing.Header_1.Header.ProposerIndex if pIdx != slashing.Header_2.Header.ProposerIndex { return fmt.Errorf("mismatched indices, received %d == %d", slashing.Header_1.Header.ProposerIndex, slashing.Header_2.Header.ProposerIndex) } if proto.Equal(slashing.Header_1.Header, slashing.Header_2.Header) { return errors.New("expected slashing headers to differ") } proposer, err := beaconState.ValidatorAtIndexReadOnly(slashing.Header_1.Header.ProposerIndex) if err != nil { return err } if !helpers.IsSlashableValidatorUsingTrie(proposer, time.CurrentEpoch(beaconState)) { return fmt.Errorf("validator with key %#x is not slashable", proposer.PublicKey()) } headers := []*ethpb.SignedBeaconBlockHeader{slashing.Header_1, slashing.Header_2} for _, header := range headers { if err := signing.ComputeDomainVerifySigningRoot(beaconState, pIdx, slots.ToEpoch(hSlot), header.Header, params.BeaconConfig().DomainBeaconProposer, header.Signature); err != nil { return errors.Wrap(err, "could not verify beacon block header") } } return nil }