Adding InititateValidatorExit (#6478)

Spec:
https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#initiate_validator_exit

Part of https://github.com/ledgerwatch/erigon/issues/5965

Co-authored-by: Giulio rebuffo <giulio.rebuffo@gmail.com>
Co-authored-by: ledgerwatch <akhounov@gmail.com>
Co-authored-by: Alex Sharp <alexsharp@Alexs-MacBook-Pro-2.local>
This commit is contained in:
Mike Neuder 2023-01-02 07:06:04 -07:00 committed by GitHub
parent 1a09dcbdb3
commit d595accc63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 207 additions and 0 deletions

View File

@ -0,0 +1,71 @@
package transition
import (
"github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state"
)
const (
FAR_FUTURE_EPOCH = (1<<64 - 1)
MAX_SEED_LOOKAHEAD = 4
MIN_PER_EPOCH_CHURN_LIMIT = 4
CHURN_LIMIT_QUOTIENT = (1 << 16)
MIN_VALIDATOR_WITHDRAWABILITY_DELAY = (1 << 8)
)
func IncreaseBalance(state *state.BeaconState, index, delta uint64) {
state.Balances()[index] += delta
}
func DecreaseBalance(state *state.BeaconState, index, delta uint64) {
curAmount := state.Balances()[index]
if curAmount < delta {
state.Balances()[index] = 0
return
}
state.Balances()[index] -= delta
}
func ComputeActivationExitEpoch(epoch uint64) uint64 {
return epoch + 1 + MAX_SEED_LOOKAHEAD
}
func GetValidtorChurnLimit(state *state.BeaconState) uint64 {
inds := GetActiveValidatorIndices(state, GetEpochAtSlot(state.Slot()))
churnLimit := len(inds) / CHURN_LIMIT_QUOTIENT
if churnLimit > MIN_PER_EPOCH_CHURN_LIMIT {
return uint64(churnLimit)
}
return MIN_PER_EPOCH_CHURN_LIMIT
}
func InitiateValidatorExit(state *state.BeaconState, index uint64) {
validator := state.ValidatorAt(int(index))
if validator.ExitEpoch != FAR_FUTURE_EPOCH {
return
}
currentEpoch := GetEpochAtSlot(state.Slot())
exitQueueEpoch := currentEpoch
activationExitEpoch := ComputeActivationExitEpoch(currentEpoch)
for _, v := range state.Validators() {
if v.ExitEpoch != FAR_FUTURE_EPOCH {
potentialExit := v.ExitEpoch + activationExitEpoch
if potentialExit > exitQueueEpoch {
exitQueueEpoch = potentialExit
}
}
}
exitQueueChurn := 0
for _, v := range state.Validators() {
if v.ExitEpoch == exitQueueEpoch {
exitQueueChurn += 1
}
}
if exitQueueChurn >= int(GetValidtorChurnLimit(state)) {
exitQueueEpoch += 1
}
validator.ExitEpoch = exitQueueEpoch
validator.WithdrawableEpoch = exitQueueEpoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
}

View File

@ -0,0 +1,136 @@
package transition
import (
"testing"
"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state"
)
const (
testExitEpoch = 53
)
func getTestStateBalances(t *testing.T) *state.BeaconState {
numVals := uint64(2048)
balances := make([]uint64, numVals)
for i := uint64(0); i < numVals; i++ {
balances[i] = i
}
return state.FromBellatrixState(&cltypes.BeaconStateBellatrix{
Balances: balances,
})
}
func getTestStateValidators(t *testing.T, numVals int) *state.BeaconState {
validators := make([]*cltypes.Validator, numVals)
for i := 0; i < numVals; i++ {
validators[i] = &cltypes.Validator{
ActivationEpoch: 0,
ExitEpoch: testExitEpoch,
}
}
return state.FromBellatrixState(&cltypes.BeaconStateBellatrix{
Slot: testExitEpoch * SLOTS_PER_EPOCH,
Validators: validators,
})
}
func TestIncreaseBalance(t *testing.T) {
state := getTestStateBalances(t)
testInd := uint64(42)
amount := uint64(100)
beforeBalance := state.Balances()[testInd]
IncreaseBalance(state, testInd, amount)
afterBalance := state.Balances()[testInd]
if afterBalance != beforeBalance+amount {
t.Errorf("unepected after balance: %d, before balance: %d, increase: %d", afterBalance, beforeBalance, amount)
}
}
func TestDecreaseBalance(t *testing.T) {
sampleState := getTestStateBalances(t)
testInd := uint64(42)
beforeBalance := sampleState.Balances()[testInd]
testCases := []struct {
description string
delta uint64
expectedBalance uint64
}{
{
description: "zero_remaining",
delta: beforeBalance,
expectedBalance: 0,
},
{
description: "non_zero_remaining",
delta: 1,
expectedBalance: beforeBalance - 1,
},
{
description: "underflow",
delta: beforeBalance + 1,
expectedBalance: 0,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
state := getTestStateBalances(t)
DecreaseBalance(state, testInd, tc.delta)
afterBalance := state.Balances()[testInd]
if afterBalance != tc.expectedBalance {
t.Errorf("unexpected resulting balance: got %d, want %d", afterBalance, tc.expectedBalance)
}
})
}
}
func TestInitiatieValidatorExit(t *testing.T) {
exitDelay := uint64(testExitEpoch + MAX_SEED_LOOKAHEAD + 1)
testCases := []struct {
description string
numValidators uint64
expectedExitEpoch uint64
expectedWithdrawlableEpoch uint64
validator *cltypes.Validator
}{
{
description: "success",
numValidators: 3,
expectedExitEpoch: testExitEpoch + exitDelay,
expectedWithdrawlableEpoch: testExitEpoch + exitDelay + MIN_VALIDATOR_WITHDRAWABILITY_DELAY,
validator: &cltypes.Validator{
ExitEpoch: FAR_FUTURE_EPOCH,
ActivationEpoch: 0,
},
},
{
description: "exit_epoch_set",
numValidators: 3,
expectedExitEpoch: testExitEpoch,
expectedWithdrawlableEpoch: testExitEpoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY,
validator: &cltypes.Validator{
ExitEpoch: testExitEpoch,
WithdrawableEpoch: testExitEpoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY,
ActivationEpoch: 0,
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
state := getTestStateValidators(t, int(tc.numValidators))
state.SetValidators(append(state.Validators(), tc.validator))
testInd := uint64(len(state.Validators()) - 1)
InitiateValidatorExit(state, testInd)
val := state.ValidatorAt(int(testInd))
if val.ExitEpoch != tc.expectedExitEpoch {
t.Errorf("unexpected exit epoch: got %d, want %d", val.ExitEpoch, tc.expectedExitEpoch)
}
if val.WithdrawableEpoch != tc.expectedWithdrawlableEpoch {
t.Errorf("unexpected withdrawable epoch: got %d, want %d", val.WithdrawableEpoch, tc.expectedWithdrawlableEpoch)
}
})
}
}