mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2025-01-09 12:31:21 +00:00
e6ba82ca0b
Passing consensus-specs tests for Capella. Processing of withdrawals and ExecutionChanges. efficient non-validation implemented. Refactored: ExecutionPayload/ExecutionPayloadHeader.
304 lines
10 KiB
Go
304 lines
10 KiB
Go
package transition
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/Giulio2002/bls"
|
|
"github.com/ledgerwatch/erigon/cl/clparams"
|
|
"github.com/ledgerwatch/erigon/cl/cltypes"
|
|
"github.com/ledgerwatch/erigon/cl/fork"
|
|
"github.com/ledgerwatch/erigon/cl/utils"
|
|
"github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state"
|
|
"github.com/ledgerwatch/erigon/core/types"
|
|
)
|
|
|
|
func isSlashableAttestationData(d1, d2 *cltypes.AttestationData) bool {
|
|
return (!d1.Equal(d2) && d1.Target.Epoch == d2.Target.Epoch) ||
|
|
(d1.Source.Epoch < d2.Source.Epoch && d2.Target.Epoch < d1.Target.Epoch)
|
|
}
|
|
|
|
func isValidIndexedAttestation(state *state.BeaconState, att *cltypes.IndexedAttestation) (bool, error) {
|
|
inds := att.AttestingIndices
|
|
if len(inds) == 0 || !utils.IsSliceSortedSet(inds) {
|
|
return false, fmt.Errorf("isValidIndexedAttestation: attesting indices are not sorted or are null")
|
|
}
|
|
|
|
pks := [][]byte{}
|
|
for _, v := range inds {
|
|
val, err := state.ValidatorAt(int(v))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
pks = append(pks, val.PublicKey[:])
|
|
}
|
|
|
|
domain, err := state.GetDomain(state.BeaconConfig().DomainBeaconAttester, att.Data.Target.Epoch)
|
|
if err != nil {
|
|
return false, fmt.Errorf("unable to get the domain: %v", err)
|
|
}
|
|
|
|
signingRoot, err := fork.ComputeSigningRoot(att.Data, domain)
|
|
if err != nil {
|
|
return false, fmt.Errorf("unable to get signing root: %v", err)
|
|
}
|
|
|
|
valid, err := bls.VerifyAggregate(att.Signature[:], signingRoot[:], pks)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error while validating signature: %v", err)
|
|
}
|
|
if !valid {
|
|
return false, fmt.Errorf("invalid aggregate signature")
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func ProcessProposerSlashing(state *state.BeaconState, propSlashing *cltypes.ProposerSlashing) error {
|
|
h1 := propSlashing.Header1.Header
|
|
h2 := propSlashing.Header2.Header
|
|
|
|
if h1.Slot != h2.Slot {
|
|
return fmt.Errorf("non-matching slots on proposer slashing: %d != %d", h1.Slot, h2.Slot)
|
|
}
|
|
|
|
if h1.ProposerIndex != h2.ProposerIndex {
|
|
return fmt.Errorf("non-matching proposer indices proposer slashing: %d != %d", h1.ProposerIndex, h2.ProposerIndex)
|
|
}
|
|
|
|
h1Root, err := h1.HashSSZ()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to hash header1: %v", err)
|
|
}
|
|
h2Root, err := h2.HashSSZ()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to hash header2: %v", err)
|
|
}
|
|
if h1Root == h2Root {
|
|
return fmt.Errorf("propose slashing headers are the same: %v == %v", h1Root, h2Root)
|
|
}
|
|
|
|
proposer, err := state.ValidatorAt(int(h1.ProposerIndex))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !proposer.IsSlashable(state.Epoch()) {
|
|
return fmt.Errorf("proposer is not slashable: %v", proposer)
|
|
}
|
|
|
|
for _, signedHeader := range []*cltypes.SignedBeaconBlockHeader{propSlashing.Header1, propSlashing.Header2} {
|
|
domain, err := state.GetDomain(state.BeaconConfig().DomainBeaconProposer, state.GetEpochAtSlot(signedHeader.Header.Slot))
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get domain: %v", err)
|
|
}
|
|
signingRoot, err := fork.ComputeSigningRoot(signedHeader.Header, domain)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to compute signing root: %v", err)
|
|
}
|
|
valid, err := bls.Verify(signedHeader.Signature[:], signingRoot[:], proposer.PublicKey[:])
|
|
if err != nil {
|
|
return fmt.Errorf("unable to verify signature: %v", err)
|
|
}
|
|
if !valid {
|
|
return fmt.Errorf("invalid signature: signature %v, root %v, pubkey %v", signedHeader.Signature[:], signingRoot[:], proposer.PublicKey[:])
|
|
}
|
|
}
|
|
|
|
// Set whistleblower index to 0 so current proposer gets reward.
|
|
state.SlashValidator(h1.ProposerIndex, nil)
|
|
return nil
|
|
}
|
|
|
|
func ProcessAttesterSlashing(state *state.BeaconState, attSlashing *cltypes.AttesterSlashing) error {
|
|
att1 := attSlashing.Attestation_1
|
|
att2 := attSlashing.Attestation_2
|
|
|
|
if !isSlashableAttestationData(att1.Data, att2.Data) {
|
|
return fmt.Errorf("attestation data not slashable: %+v; %+v", att1.Data, att2.Data)
|
|
}
|
|
|
|
valid, err := isValidIndexedAttestation(state, att1)
|
|
if err != nil {
|
|
return fmt.Errorf("error calculating indexed attestation 1 validity: %v", err)
|
|
}
|
|
if !valid {
|
|
return fmt.Errorf("invalid indexed attestation 1")
|
|
}
|
|
|
|
valid, err = isValidIndexedAttestation(state, att2)
|
|
if err != nil {
|
|
return fmt.Errorf("error calculating indexed attestation 2 validity: %v", err)
|
|
}
|
|
if !valid {
|
|
return fmt.Errorf("invalid indexed attestation 2")
|
|
}
|
|
|
|
slashedAny := false
|
|
currentEpoch := state.GetEpochAtSlot(state.Slot())
|
|
for _, ind := range utils.IntersectionOfSortedSets(att1.AttestingIndices, att2.AttestingIndices) {
|
|
validator, err := state.ValidatorAt(int(ind))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if validator.IsSlashable(currentEpoch) {
|
|
err := state.SlashValidator(ind, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to slash validator: %d", ind)
|
|
}
|
|
slashedAny = true
|
|
}
|
|
}
|
|
|
|
if !slashedAny {
|
|
return fmt.Errorf("no validators slashed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ProcessDeposit(state *state.BeaconState, deposit *cltypes.Deposit, fullValidation bool) error {
|
|
if deposit == nil {
|
|
return nil
|
|
}
|
|
depositLeaf, err := deposit.Data.HashSSZ()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
depositIndex := state.Eth1DepositIndex()
|
|
eth1Data := state.Eth1Data()
|
|
// Validate merkle proof for deposit leaf.
|
|
if fullValidation && !utils.IsValidMerkleBranch(
|
|
depositLeaf,
|
|
deposit.Proof,
|
|
state.BeaconConfig().DepositContractTreeDepth+1,
|
|
depositIndex,
|
|
eth1Data.Root,
|
|
) {
|
|
return fmt.Errorf("processDepositForAltair: Could not validate deposit root")
|
|
}
|
|
|
|
// Increment index
|
|
state.SetEth1DepositIndex(depositIndex + 1)
|
|
publicKey := deposit.Data.PubKey
|
|
amount := deposit.Data.Amount
|
|
// Check if pub key is in validator set
|
|
validatorIndex, has := state.ValidatorIndexByPubkey(publicKey)
|
|
if !has {
|
|
// Agnostic domain.
|
|
domain, err := fork.ComputeDomain(state.BeaconConfig().DomainDeposit[:], utils.Uint32ToBytes4(state.BeaconConfig().GenesisForkVersion), [32]byte{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
depositMessageRoot, err := deposit.Data.MessageHash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signedRoot := utils.Keccak256(depositMessageRoot[:], domain)
|
|
// Perform BLS verification and if successful noice.
|
|
valid, err := bls.Verify(deposit.Data.Signature[:], signedRoot[:], publicKey[:])
|
|
// Literally you can input it trash.
|
|
if !valid || err != nil {
|
|
return nil
|
|
}
|
|
// Append validator
|
|
state.AddValidator(state.ValidatorFromDeposit(deposit), amount)
|
|
// Altair forward
|
|
if state.Version() >= clparams.AltairVersion {
|
|
state.AddCurrentEpochParticipationFlags(cltypes.ParticipationFlags(0))
|
|
state.AddPreviousEpochParticipationFlags(cltypes.ParticipationFlags(0))
|
|
state.AddInactivityScore(0)
|
|
}
|
|
return nil
|
|
}
|
|
// Increase the balance if exists already
|
|
return state.IncreaseBalance(validatorIndex, amount)
|
|
}
|
|
|
|
// ProcessVoluntaryExit takes a voluntary exit and applies state transition.
|
|
func ProcessVoluntaryExit(state *state.BeaconState, signedVoluntaryExit *cltypes.SignedVoluntaryExit, fullValidation bool) error {
|
|
// Sanity checks so that we know it is good.
|
|
voluntaryExit := signedVoluntaryExit.VolunaryExit
|
|
currentEpoch := state.Epoch()
|
|
validator, err := state.ValidatorAt(int(voluntaryExit.ValidatorIndex))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !validator.Active(currentEpoch) {
|
|
return errors.New("ProcessVoluntaryExit: validator is not active")
|
|
}
|
|
if validator.ExitEpoch != state.BeaconConfig().FarFutureEpoch {
|
|
return errors.New("ProcessVoluntaryExit: another exit for the same validator is already getting processed")
|
|
}
|
|
if currentEpoch < voluntaryExit.Epoch {
|
|
return errors.New("ProcessVoluntaryExit: exit is happening in the future")
|
|
}
|
|
if currentEpoch < validator.ActivationEpoch+state.BeaconConfig().ShardCommitteePeriod {
|
|
return errors.New("ProcessVoluntaryExit: exit is happening too fast")
|
|
}
|
|
|
|
// We can skip it in some instances if we want to optimistically sync up.
|
|
if fullValidation {
|
|
domain, err := state.GetDomain(state.BeaconConfig().DomainVoluntaryExit, voluntaryExit.Epoch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signingRoot, err := fork.ComputeSigningRoot(voluntaryExit, domain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
valid, err := bls.Verify(signedVoluntaryExit.Signature[:], signingRoot[:], validator.PublicKey[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !valid {
|
|
return errors.New("ProcessVoluntaryExit: BLS verification failed")
|
|
}
|
|
}
|
|
// Do the exit (same process in slashing).
|
|
return state.InitiateValidatorExit(voluntaryExit.ValidatorIndex)
|
|
}
|
|
|
|
// ProcessWithdrawals processes withdrawals by decreasing the balance of each validator
|
|
// and updating the next withdrawal index and validator index.
|
|
func ProcessWithdrawals(state *state.BeaconState, withdrawals types.Withdrawals, fullValidation bool) error {
|
|
// Get the list of withdrawals, the expected withdrawals (if performing full validation),
|
|
// and the beacon configuration.
|
|
beaconConfig := state.BeaconConfig()
|
|
numValidators := uint64(len(state.Validators()))
|
|
|
|
// Check if full validation is required and verify expected withdrawals.
|
|
if fullValidation {
|
|
expectedWithdrawals := state.ExpectedWithdrawals()
|
|
if len(expectedWithdrawals) != len(withdrawals) {
|
|
return fmt.Errorf("ProcessWithdrawals: expected %d withdrawals, but got %d", len(expectedWithdrawals), len(withdrawals))
|
|
}
|
|
for i, withdrawal := range withdrawals {
|
|
if !expectedWithdrawals[i].Equal(withdrawal) {
|
|
return fmt.Errorf("ProcessWithdrawals: withdrawal %d does not match expected withdrawal", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decrease the balance of each validator for the corresponding withdrawal.
|
|
for _, withdrawal := range withdrawals {
|
|
if err := state.DecreaseBalance(withdrawal.Validator, withdrawal.Amount); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Update next withdrawal index based on number of withdrawals.
|
|
if len(withdrawals) > 0 {
|
|
lastWithdrawalIndex := withdrawals[len(withdrawals)-1].Index
|
|
state.SetNextWithdrawalIndex(lastWithdrawalIndex + 1)
|
|
}
|
|
|
|
// Update next withdrawal validator index based on number of withdrawals.
|
|
if len(withdrawals) == int(beaconConfig.MaxWithdrawalsPerPayload) {
|
|
lastWithdrawalValidatorIndex := withdrawals[len(withdrawals)-1].Validator + 1
|
|
state.SetNextWithdrawalValidatorIndex(lastWithdrawalValidatorIndex % numValidators)
|
|
} else {
|
|
nextIndex := state.NextWithdrawalValidatorIndex() + beaconConfig.MaxValidatorsPerWithdrawalsSweep
|
|
state.SetNextWithdrawalValidatorIndex(nextIndex % numValidators)
|
|
}
|
|
|
|
return nil
|
|
}
|