mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 04:54:05 -05:00
* Add log capitalization analyzer and apply fixes across codebase Implements a new nogo analyzer to enforce proper log message capitalization and applies the fixes to all affected log statements throughout the beacon chain, validator, and supporting components. Co-Authored-By: Claude <noreply@anthropic.com> * Radek's feedback --------- Co-authored-by: Claude <noreply@anthropic.com>
390 lines
13 KiB
Go
390 lines
13 KiB
Go
package electra
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers"
|
|
"github.com/OffchainLabs/prysm/v6/beacon-chain/state"
|
|
state_native "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native"
|
|
"github.com/OffchainLabs/prysm/v6/config/params"
|
|
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
|
|
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
|
|
"github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
|
|
enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1"
|
|
eth "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v6/time/slots"
|
|
"github.com/ethereum/go-ethereum/common/math"
|
|
"github.com/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ProcessPendingConsolidations implements the spec definition below. This method makes mutating
|
|
// calls to the beacon state.
|
|
//
|
|
// Spec definition:
|
|
//
|
|
// def process_pending_consolidations(state: BeaconState) -> None:
|
|
//
|
|
// next_epoch = Epoch(get_current_epoch(state) + 1)
|
|
// next_pending_consolidation = 0
|
|
// for pending_consolidation in state.pending_consolidations:
|
|
// source_validator = state.validators[pending_consolidation.source_index]
|
|
// if source_validator.slashed:
|
|
// next_pending_consolidation += 1
|
|
// continue
|
|
// if source_validator.withdrawable_epoch > next_epoch:
|
|
// break
|
|
//
|
|
// # Calculate the consolidated balance
|
|
// source_effective_balance = min(state.balances[pending_consolidation.source_index], source_validator.effective_balance)
|
|
//
|
|
// # Move active balance to target. Excess balance is withdrawable.
|
|
// decrease_balance(state, pending_consolidation.source_index, source_effective_balance)
|
|
// increase_balance(state, pending_consolidation.target_index, source_effective_balance)
|
|
// next_pending_consolidation += 1
|
|
//
|
|
// state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:]
|
|
func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) error {
|
|
_, span := trace.StartSpan(ctx, "electra.ProcessPendingConsolidations")
|
|
defer span.End()
|
|
|
|
if st == nil || st.IsNil() {
|
|
return errors.New("nil state")
|
|
}
|
|
|
|
nextEpoch := slots.ToEpoch(st.Slot()) + 1
|
|
|
|
pendingConsolidations, err := st.PendingConsolidations()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var nextPendingConsolidation uint64
|
|
for _, pc := range pendingConsolidations {
|
|
sourceValidator, err := st.ValidatorAtIndexReadOnly(pc.SourceIndex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sourceValidator.Slashed() {
|
|
nextPendingConsolidation++
|
|
continue
|
|
}
|
|
if sourceValidator.WithdrawableEpoch() > nextEpoch {
|
|
break
|
|
}
|
|
|
|
validatorBalance, err := st.BalanceAtIndex(pc.SourceIndex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b := min(validatorBalance, sourceValidator.EffectiveBalance())
|
|
|
|
if err := helpers.DecreaseBalance(st, pc.SourceIndex, b); err != nil {
|
|
return err
|
|
}
|
|
if err := helpers.IncreaseBalance(st, pc.TargetIndex, b); err != nil {
|
|
return err
|
|
}
|
|
nextPendingConsolidation++
|
|
}
|
|
|
|
if nextPendingConsolidation > 0 {
|
|
return st.SetPendingConsolidations(pendingConsolidations[nextPendingConsolidation:])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProcessConsolidationRequests implements the spec definition below. This method makes mutating
|
|
// calls to the beacon state.
|
|
//
|
|
// def process_consolidation_request(
|
|
// state: BeaconState,
|
|
// consolidation_request: ConsolidationRequest
|
|
// ) -> None:
|
|
// if is_valid_switch_to_compounding_request(state, consolidation_request):
|
|
// validator_pubkeys = [v.pubkey for v in state.validators]
|
|
// request_source_pubkey = consolidation_request.source_pubkey
|
|
// source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
|
|
// switch_to_compounding_validator(state, source_index)
|
|
// return
|
|
//
|
|
// # Verify that source != target, so a consolidation cannot be used as an exit.
|
|
// if consolidation_request.source_pubkey == consolidation_request.target_pubkey:
|
|
// return
|
|
// # If the pending consolidations queue is full, consolidation requests are ignored
|
|
// if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT:
|
|
// return
|
|
// # If there is too little available consolidation churn limit, consolidation requests are ignored
|
|
// if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE:
|
|
// return
|
|
//
|
|
// validator_pubkeys = [v.pubkey for v in state.validators]
|
|
// # Verify pubkeys exists
|
|
// request_source_pubkey = consolidation_request.source_pubkey
|
|
// request_target_pubkey = consolidation_request.target_pubkey
|
|
// if request_source_pubkey not in validator_pubkeys:
|
|
// return
|
|
// if request_target_pubkey not in validator_pubkeys:
|
|
// return
|
|
// source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
|
|
// target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey))
|
|
// source_validator = state.validators[source_index]
|
|
// target_validator = state.validators[target_index]
|
|
//
|
|
// # Verify source withdrawal credentials
|
|
// has_correct_credential = has_execution_withdrawal_credential(source_validator)
|
|
// is_correct_source_address = (
|
|
// source_validator.withdrawal_credentials[12:] == consolidation_request.source_address
|
|
// )
|
|
// if not (has_correct_credential and is_correct_source_address):
|
|
// return
|
|
//
|
|
// # Verify that target has compounding withdrawal credentials
|
|
// if not has_compounding_withdrawal_credential(target_validator):
|
|
// return
|
|
//
|
|
// # Verify the source and the target are active
|
|
// current_epoch = get_current_epoch(state)
|
|
// if not is_active_validator(source_validator, current_epoch):
|
|
// return
|
|
// if not is_active_validator(target_validator, current_epoch):
|
|
// return
|
|
// # Verify exits for source and target have not been initiated
|
|
// if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
|
|
// return
|
|
// if target_validator.exit_epoch != FAR_FUTURE_EPOCH:
|
|
// return
|
|
//
|
|
// # Verify the source has been active long enough
|
|
// if current_epoch < source_validator.activation_epoch + SHARD_COMMITTEE_PERIOD:
|
|
// return
|
|
//
|
|
// # Verify the source has no pending withdrawals in the queue
|
|
// if get_pending_balance_to_withdraw(state, source_index) > 0:
|
|
// return
|
|
// # Initiate source validator exit and append pending consolidation
|
|
// source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(
|
|
// state, source_validator.effective_balance
|
|
// )
|
|
// source_validator.withdrawable_epoch = Epoch(
|
|
// source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
|
|
// )
|
|
// state.pending_consolidations.append(PendingConsolidation(
|
|
// source_index=source_index,
|
|
// target_index=target_index
|
|
// ))
|
|
func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, reqs []*enginev1.ConsolidationRequest) error {
|
|
if len(reqs) == 0 || st == nil {
|
|
return nil
|
|
}
|
|
curEpoch := slots.ToEpoch(st.Slot())
|
|
ffe := params.BeaconConfig().FarFutureEpoch
|
|
minValWithdrawDelay := params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
|
pcLimit := params.BeaconConfig().PendingConsolidationsLimit
|
|
|
|
for _, cr := range reqs {
|
|
if cr == nil {
|
|
return errors.New("nil consolidation request")
|
|
}
|
|
if ctx.Err() != nil {
|
|
return fmt.Errorf("cannot process consolidation requests: %w", ctx.Err())
|
|
}
|
|
if IsValidSwitchToCompoundingRequest(st, cr) {
|
|
srcIdx, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(cr.SourcePubkey))
|
|
if !ok {
|
|
log.Error("Failed to find source validator index")
|
|
continue
|
|
}
|
|
if err := SwitchToCompoundingValidator(st, srcIdx); err != nil {
|
|
log.WithError(err).Error("Failed to switch to compounding validator")
|
|
}
|
|
continue
|
|
}
|
|
sourcePubkey := bytesutil.ToBytes48(cr.SourcePubkey)
|
|
targetPubkey := bytesutil.ToBytes48(cr.TargetPubkey)
|
|
if sourcePubkey == targetPubkey {
|
|
continue
|
|
}
|
|
|
|
if npc, err := st.NumPendingConsolidations(); err != nil {
|
|
return fmt.Errorf("failed to fetch number of pending consolidations: %w", err) // This should never happen.
|
|
} else if npc >= pcLimit {
|
|
continue
|
|
}
|
|
|
|
activeBal, err := helpers.TotalActiveBalance(st)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
churnLimit := helpers.ConsolidationChurnLimit(primitives.Gwei(activeBal))
|
|
if churnLimit <= primitives.Gwei(params.BeaconConfig().MinActivationBalance) {
|
|
continue
|
|
}
|
|
|
|
srcIdx, ok := st.ValidatorIndexByPubkey(sourcePubkey)
|
|
if !ok {
|
|
continue
|
|
}
|
|
tgtIdx, ok := st.ValidatorIndexByPubkey(targetPubkey)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
srcV, err := st.ValidatorAtIndex(srcIdx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch source validator: %w", err) // This should never happen.
|
|
}
|
|
|
|
roSrcV, err := state_native.NewValidator(srcV)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tgtV, err := st.ValidatorAtIndexReadOnly(tgtIdx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch target validator: %w", err) // This should never happen.
|
|
}
|
|
|
|
// Verify source withdrawal credentials
|
|
if !roSrcV.HasExecutionWithdrawalCredentials() {
|
|
continue
|
|
}
|
|
// Confirm source_validator.withdrawal_credentials[12:] == consolidation_request.source_address
|
|
if len(srcV.WithdrawalCredentials) != 32 || len(cr.SourceAddress) != 20 || !bytes.HasSuffix(srcV.WithdrawalCredentials, cr.SourceAddress) {
|
|
continue
|
|
}
|
|
|
|
// Target validator must have their withdrawal credentials set appropriately.
|
|
if !tgtV.HasCompoundingWithdrawalCredentials() {
|
|
continue
|
|
}
|
|
|
|
// Both validators must be active.
|
|
if !helpers.IsActiveValidator(srcV, curEpoch) || !helpers.IsActiveValidatorUsingTrie(tgtV, curEpoch) {
|
|
continue
|
|
}
|
|
// Neither validator is exiting.
|
|
if srcV.ExitEpoch != ffe || tgtV.ExitEpoch() != ffe {
|
|
continue
|
|
}
|
|
|
|
e, overflow := math.SafeAdd(uint64(srcV.ActivationEpoch), uint64(params.BeaconConfig().ShardCommitteePeriod))
|
|
if overflow {
|
|
log.Error("Overflow when adding activation epoch and shard committee period")
|
|
continue
|
|
}
|
|
if uint64(curEpoch) < e {
|
|
continue
|
|
}
|
|
bal, err := st.PendingBalanceToWithdraw(srcIdx)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to fetch pending balance to withdraw")
|
|
continue
|
|
}
|
|
if bal > 0 {
|
|
continue
|
|
}
|
|
|
|
// Initiate the exit of the source validator.
|
|
exitEpoch, err := ComputeConsolidationEpochAndUpdateChurn(ctx, st, primitives.Gwei(srcV.EffectiveBalance))
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to compute consolidation epoch")
|
|
continue
|
|
}
|
|
srcV.ExitEpoch = exitEpoch
|
|
srcV.WithdrawableEpoch = exitEpoch + minValWithdrawDelay
|
|
if err := st.UpdateValidatorAtIndex(srcIdx, srcV); err != nil {
|
|
return fmt.Errorf("failed to update validator: %w", err) // This should never happen.
|
|
}
|
|
|
|
if err := st.AppendPendingConsolidation(ð.PendingConsolidation{SourceIndex: srcIdx, TargetIndex: tgtIdx}); err != nil {
|
|
return fmt.Errorf("failed to append pending consolidation: %w", err) // This should never happen.
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsValidSwitchToCompoundingRequest returns true if the given consolidation request is valid for switching to compounding.
|
|
//
|
|
// Spec code:
|
|
//
|
|
// def is_valid_switch_to_compounding_request(
|
|
//
|
|
// state: BeaconState,
|
|
// consolidation_request: ConsolidationRequest
|
|
//
|
|
// ) -> bool:
|
|
//
|
|
// # Switch to compounding requires source and target be equal
|
|
// if consolidation_request.source_pubkey != consolidation_request.target_pubkey:
|
|
// return False
|
|
//
|
|
// # Verify pubkey exists
|
|
// source_pubkey = consolidation_request.source_pubkey
|
|
// validator_pubkeys = [v.pubkey for v in state.validators]
|
|
// if source_pubkey not in validator_pubkeys:
|
|
// return False
|
|
//
|
|
// source_validator = state.validators[ValidatorIndex(validator_pubkeys.index(source_pubkey))]
|
|
//
|
|
// # Verify request has been authorized
|
|
// if source_validator.withdrawal_credentials[12:] != consolidation_request.source_address:
|
|
// return False
|
|
//
|
|
// # Verify source withdrawal credentials
|
|
// if not has_eth1_withdrawal_credential(source_validator):
|
|
// return False
|
|
//
|
|
// # Verify the source is active
|
|
// current_epoch = get_current_epoch(state)
|
|
// if not is_active_validator(source_validator, current_epoch):
|
|
// return False
|
|
//
|
|
// # Verify exit for source has not been initiated
|
|
// if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
|
|
// return False
|
|
//
|
|
// return True
|
|
func IsValidSwitchToCompoundingRequest(st state.BeaconState, req *enginev1.ConsolidationRequest) bool {
|
|
if req.SourcePubkey == nil || req.TargetPubkey == nil {
|
|
return false
|
|
}
|
|
|
|
if !bytes.Equal(req.SourcePubkey, req.TargetPubkey) {
|
|
return false
|
|
}
|
|
|
|
srcIdx, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(req.SourcePubkey))
|
|
if !ok {
|
|
return false
|
|
}
|
|
// As per the consensus specification, this error is not considered an assertion.
|
|
// Therefore, if the source_pubkey is not found in validator_pubkeys, we simply return false.
|
|
srcV, err := st.ValidatorAtIndexReadOnly(srcIdx)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
sourceAddress := req.SourceAddress
|
|
withdrawalCreds := srcV.GetWithdrawalCredentials()
|
|
if len(withdrawalCreds) != 32 || len(sourceAddress) != 20 || !bytes.HasSuffix(withdrawalCreds, sourceAddress) {
|
|
return false
|
|
}
|
|
|
|
if !srcV.HasETH1WithdrawalCredentials() {
|
|
return false
|
|
}
|
|
|
|
curEpoch := slots.ToEpoch(st.Slot())
|
|
if !helpers.IsActiveValidatorUsingTrie(srcV, curEpoch) {
|
|
return false
|
|
}
|
|
|
|
if srcV.ExitEpoch() != params.BeaconConfig().FarFutureEpoch {
|
|
return false
|
|
}
|
|
return true
|
|
}
|