Compare commits

...

7 Commits

Author SHA1 Message Date
Preston Van Loon
bb53d3cbb6 Debugging failing spectests 2024-07-12 11:43:48 -05:00
Preston Van Loon
5c918dd710 Unskip electra spectests 2024-07-12 11:43:48 -05:00
Preston Van Loon
8cf9c22e4c mega merge commit lmao 2024-07-12 11:43:48 -05:00
Preston Van Loon
e0c7c5e630 Electra: Forkchoice spectest fix 2024-07-12 11:39:57 -05:00
Potuz
31f7604c88 Fix Merkle proof generator for Electra 2024-07-12 09:56:29 -03:00
Preston Van Loon
5757e0b159 Unit tests for new process_withdrawal logic in electra 2024-07-11 13:38:05 -05:00
Preston Van Loon
50ffb535be Electra: EIP-7251 implement process_withdrawal updates 2024-07-11 13:38:05 -05:00
35 changed files with 343 additions and 149 deletions

View File

@@ -120,32 +120,35 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si
//
// Spec pseudocode definition:
//
// def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
// def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
// expected_withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) # [Modified in Electra:EIP7251]
//
// expected_withdrawals = get_expected_withdrawals(state)
// assert len(payload.withdrawals) == len(expected_withdrawals)
// assert len(payload.withdrawals) == len(expected_withdrawals)
//
// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
// assert withdrawal == expected_withdrawal
// decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
// assert withdrawal == expected_withdrawal
// decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
//
// # Update the next withdrawal index if this block contained withdrawals
// if len(expected_withdrawals) != 0:
// latest_withdrawal = expected_withdrawals[-1]
// state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
// # Update pending partial withdrawals [New in Electra:EIP7251]
// state.pending_partial_withdrawals = state.pending_partial_withdrawals[partial_withdrawals_count:]
//
// # Update the next validator index to start the next withdrawal sweep
// if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
// # Next sweep starts after the latest withdrawal's validator index
// next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators))
// state.next_withdrawal_validator_index = next_validator_index
// else:
// # Advance sweep by the max length of the sweep if there was not a full set of withdrawals
// next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
// next_validator_index = ValidatorIndex(next_index % len(state.validators))
// state.next_withdrawal_validator_index = next_validator_index
// # Update the next withdrawal index if this block contained withdrawals
// if len(expected_withdrawals) != 0:
// latest_withdrawal = expected_withdrawals[-1]
// state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
//
// # Update the next validator index to start the next withdrawal sweep
// if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
// # Next sweep starts after the latest withdrawal's validator index
// next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators))
// state.next_withdrawal_validator_index = next_validator_index
// else:
// # Advance sweep by the max length of the sweep if there was not a full set of withdrawals
// next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
// next_validator_index = ValidatorIndex(next_index % len(state.validators))
// state.next_withdrawal_validator_index = next_validator_index
func ProcessWithdrawals(st state.BeaconState, executionData interfaces.ExecutionData) (state.BeaconState, error) {
expectedWithdrawals, _, err := st.ExpectedWithdrawals()
expectedWithdrawals, partialWithdrawalsCount, err := st.ExpectedWithdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get expected withdrawals")
}
@@ -162,6 +165,11 @@ func ProcessWithdrawals(st state.BeaconState, executionData interfaces.Execution
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
if len(wds) != len(expectedWithdrawals) {
return nil, fmt.Errorf("execution payload header has %d withdrawals when %d were expected", len(wds), len(expectedWithdrawals))
}
wdRoot, err = ssz.WithdrawalSliceRoot(wds, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
@@ -173,6 +181,9 @@ func ProcessWithdrawals(st state.BeaconState, executionData interfaces.Execution
return nil, errors.Wrap(err, "could not get expected withdrawals root")
}
if expectedRoot != wdRoot {
if true {
panic("moo")
}
return nil, fmt.Errorf("expected withdrawals root %#x, got %#x", expectedRoot, wdRoot)
}
@@ -182,6 +193,13 @@ func ProcessWithdrawals(st state.BeaconState, executionData interfaces.Execution
return nil, errors.Wrap(err, "could not decrease balance")
}
}
if st.Version() >= version.Electra {
if err := st.DequeuePartialWithdrawals(partialWithdrawalsCount); err != nil {
return nil, fmt.Errorf("unable to dequeue partial withdrawals from state: %w", err)
}
}
if len(expectedWithdrawals) > 0 {
if err := st.SetNextWithdrawalIndex(expectedWithdrawals[len(expectedWithdrawals)-1].Index + 1); err != nil {
return nil, errors.Wrap(err, "could not set next withdrawal index")

View File

@@ -12,6 +12,7 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/crypto/bls/common"
@@ -19,6 +20,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
@@ -675,6 +677,7 @@ func TestProcessWithdrawals(t *testing.T) {
FullWithdrawalIndices []primitives.ValidatorIndex
PendingPartialWithdrawalIndices []primitives.ValidatorIndex
Withdrawals []*enginev1.Withdrawal
PendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal // Electra
}
type control struct {
NextWithdrawalValidatorIndex primitives.ValidatorIndex
@@ -772,7 +775,7 @@ func TestProcessWithdrawals(t *testing.T) {
},
{
Args: args{
Name: "Less than max sweep at end",
Name: "less than max sweep at end",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 4,
FullWithdrawalIndices: []primitives.ValidatorIndex{80, 81, 82, 83},
@@ -789,7 +792,7 @@ func TestProcessWithdrawals(t *testing.T) {
},
{
Args: args{
Name: "Less than max sweep and beginning",
Name: "less than max sweep and beginning",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 4,
FullWithdrawalIndices: []primitives.ValidatorIndex{4, 5, 6},
@@ -846,6 +849,36 @@ func TestProcessWithdrawals(t *testing.T) {
},
},
},
{
Args: args{
Name: "success many withdrawals with pending partial withdrawals in state",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 88,
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28},
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{2, 1, 89, 15},
Withdrawals: []*enginev1.Withdrawal{
PendingPartialWithdrawal(89, 22), PendingPartialWithdrawal(1, 23), PendingPartialWithdrawal(2, 24),
fullWithdrawal(7, 25), PendingPartialWithdrawal(15, 26), fullWithdrawal(19, 27),
fullWithdrawal(28, 28),
},
PendingPartialWithdrawals: []*ethpb.PendingPartialWithdrawal{
{
Index: 11,
Amount: withdrawalAmount(11) - maxEffectiveBalance,
},
},
},
Control: control{
NextWithdrawalValidatorIndex: 40,
NextWithdrawalIndex: 29,
Balances: map[uint64]uint64{
7: 0, 19: 0, 28: 0,
2: maxEffectiveBalance, 1: maxEffectiveBalance, 89: maxEffectiveBalance,
15: maxEffectiveBalance,
},
},
},
{
Args: args{
Name: "success more than max fully withdrawals",
@@ -1011,65 +1044,97 @@ func TestProcessWithdrawals(t *testing.T) {
}
}
prepareValidators := func(st *ethpb.BeaconStateCapella, arguments args) (state.BeaconState, error) {
prepareValidators := func(st state.BeaconState, arguments args) error {
validators := make([]*ethpb.Validator, numValidators)
st.Balances = make([]uint64, numValidators)
if err := st.SetBalances(make([]uint64, numValidators)); err != nil {
return err
}
for i := range validators {
v := &ethpb.Validator{}
v.EffectiveBalance = maxEffectiveBalance
v.WithdrawableEpoch = epochInFuture
v.WithdrawalCredentials = make([]byte, 32)
v.WithdrawalCredentials[31] = byte(i)
st.Balances[i] = v.EffectiveBalance - uint64(rand.Intn(1000))
if err := st.UpdateBalancesAtIndex(primitives.ValidatorIndex(i), v.EffectiveBalance-uint64(rand.Intn(1000))); err != nil {
return err
}
validators[i] = v
}
for _, idx := range arguments.FullWithdrawalIndices {
if idx != notWithdrawableIndex {
validators[idx].WithdrawableEpoch = epochInPast
}
st.Balances[idx] = withdrawalAmount(idx)
if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil {
return err
}
validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
}
for _, idx := range arguments.PendingPartialWithdrawalIndices {
validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
st.Balances[idx] = withdrawalAmount(idx)
if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil {
return err
}
}
st.Validators = validators
return state_native.InitializeFromProtoCapella(st)
return st.SetValidators(validators)
}
for _, test := range tests {
t.Run(test.Args.Name, func(t *testing.T) {
saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = maxSweep
if test.Args.Withdrawals == nil {
test.Args.Withdrawals = make([]*enginev1.Withdrawal, 0)
for _, fork := range []int{version.Capella, version.Electra} {
t.Run(version.String(fork), func(t *testing.T) {
saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = maxSweep
if test.Args.Withdrawals == nil {
test.Args.Withdrawals = make([]*enginev1.Withdrawal, 0)
}
if test.Args.FullWithdrawalIndices == nil {
test.Args.FullWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
}
if test.Args.PendingPartialWithdrawalIndices == nil {
test.Args.PendingPartialWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
}
slot, err := slots.EpochStart(currentEpoch)
require.NoError(t, err)
var st state.BeaconState
var p interfaces.ExecutionData
switch fork {
case version.Capella:
spb := &ethpb.BeaconStateCapella{
Slot: slot,
NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex,
NextWithdrawalIndex: test.Args.NextWithdrawalIndex,
}
st, err = state_native.InitializeFromProtoUnsafeCapella(spb)
require.NoError(t, err)
p, err = consensusblocks.WrappedExecutionPayloadCapella(&enginev1.ExecutionPayloadCapella{Withdrawals: test.Args.Withdrawals})
require.NoError(t, err)
case version.Electra:
spb := &ethpb.BeaconStateElectra{
Slot: slot,
NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex,
NextWithdrawalIndex: test.Args.NextWithdrawalIndex,
PendingPartialWithdrawals: test.Args.PendingPartialWithdrawals,
}
st, err = state_native.InitializeFromProtoUnsafeElectra(spb)
require.NoError(t, err)
p, err = consensusblocks.WrappedExecutionPayloadElectra(&enginev1.ExecutionPayloadElectra{Withdrawals: test.Args.Withdrawals})
require.NoError(t, err)
default:
t.Fatalf("Add a beacon state setup for version %s", version.String(fork))
}
err = prepareValidators(st, test.Args)
require.NoError(t, err)
post, err := blocks.ProcessWithdrawals(st, p)
if test.Control.ExpectedError {
require.NotNil(t, err)
} else {
require.NoError(t, err)
checkPostState(t, test.Control, post)
}
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved
})
}
if test.Args.FullWithdrawalIndices == nil {
test.Args.FullWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
}
if test.Args.PendingPartialWithdrawalIndices == nil {
test.Args.PendingPartialWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
}
slot, err := slots.EpochStart(currentEpoch)
require.NoError(t, err)
spb := &ethpb.BeaconStateCapella{
Slot: slot,
NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex,
NextWithdrawalIndex: test.Args.NextWithdrawalIndex,
}
st, err := prepareValidators(spb, test.Args)
require.NoError(t, err)
p, err := consensusblocks.WrappedExecutionPayloadCapella(&enginev1.ExecutionPayloadCapella{Withdrawals: test.Args.Withdrawals})
require.NoError(t, err)
post, err := blocks.ProcessWithdrawals(st, p)
if test.Control.ExpectedError {
require.NotNil(t, err)
} else {
require.NoError(t, err)
checkPostState(t, test.Control, post)
}
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved
})
}
}

View File

@@ -2,6 +2,7 @@ package electra
import (
"context"
"errors"
"fmt"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
@@ -84,7 +85,7 @@ func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) error {
var err error
// exitQueueEpoch and churn arguments are not used in electra.
st, _, err = validators.InitiateValidatorExit(ctx, st, idx, 0 /*exitQueueEpoch*/, 0 /*churn*/)
if err != nil {
if err != nil && !errors.Is(err, validators.ErrValidatorAlreadyExited) {
return fmt.Errorf("failed to initiate validator exit at index %d: %w", idx, err)
}
}

View File

@@ -93,6 +93,8 @@ func ProcessOperations(
return nil, errors.Wrap(err, "could not process deposit receipts")
}
// TODO: Process consolidations from execution header.
if err := ProcessConsolidationRequests(ctx, st, exe.ConsolidationRequests()); err != nil {
return nil, errors.Wrap(err, "could not process consolidations")
}
return st, nil
}

View File

@@ -26,66 +26,67 @@ import (
//
// def process_withdrawal_request(
//
// state: BeaconState,
// withdrawal_request: WithdrawalRequest
// state: BeaconState,
// withdrawal_request: WithdrawalRequest
//
// ) -> None:
// amount = execution_layer_withdrawal_request.amount
// is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT
// ) -> None:
//
// # If partial withdrawal queue is full, only full exits are processed
// if len(state.pending_partial_withdrawals) == PENDING_PARTIAL_WITHDRAWALS_LIMIT and not is_full_exit_request:
// return
// amount = withdrawal_request.amount
// is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT
//
// validator_pubkeys = [v.pubkey for v in state.validators]
// # Verify pubkey exists
// request_pubkey = execution_layer_withdrawal_request.validator_pubkey
// if request_pubkey not in validator_pubkeys:
// return
// index = ValidatorIndex(validator_pubkeys.index(request_pubkey))
// validator = state.validators[index]
// # If partial withdrawal queue is full, only full exits are processed
// if len(state.pending_partial_withdrawals) == PENDING_PARTIAL_WITHDRAWALS_LIMIT and not is_full_exit_request:
// return
//
// # Verify withdrawal credentials
// has_correct_credential = has_execution_withdrawal_credential(validator)
// is_correct_source_address = (
// validator.withdrawal_credentials[12:] == execution_layer_withdrawal_request.source_address
// )
// if not (has_correct_credential and is_correct_source_address):
// return
// # Verify the validator is active
// if not is_active_validator(validator, get_current_epoch(state)):
// return
// # Verify exit has not been initiated
// if validator.exit_epoch != FAR_FUTURE_EPOCH:
// return
// # Verify the validator has been active long enough
// if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD:
// return
// validator_pubkeys = [v.pubkey for v in state.validators]
// # Verify pubkey exists
// request_pubkey = withdrawal_request.validator_pubkey
// if request_pubkey not in validator_pubkeys:
// return
// index = ValidatorIndex(validator_pubkeys.index(request_pubkey))
// validator = state.validators[index]
//
// pending_balance_to_withdraw = get_pending_balance_to_withdraw(state, index)
// # Verify withdrawal credentials
// has_correct_credential = has_execution_withdrawal_credential(validator)
// is_correct_source_address = (
// validator.withdrawal_credentials[12:] == withdrawal_request.source_address
// )
// if not (has_correct_credential and is_correct_source_address):
// return
// # Verify the validator is active
// if not is_active_validator(validator, get_current_epoch(state)):
// return
// # Verify exit has not been initiated
// if validator.exit_epoch != FAR_FUTURE_EPOCH:
// return
// # Verify the validator has been active long enough
// if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD:
// return
//
// if is_full_exit_request:
// # Only exit validator if it has no pending withdrawals in the queue
// if pending_balance_to_withdraw == 0:
// initiate_validator_exit(state, index)
// return
// pending_balance_to_withdraw = get_pending_balance_to_withdraw(state, index)
//
// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
// has_excess_balance = state.balances[index] > MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw
// if is_full_exit_request:
// # Only exit validator if it has no pending withdrawals in the queue
// if pending_balance_to_withdraw == 0:
// initiate_validator_exit(state, index)
// return
//
// # Only allow partial withdrawals with compounding withdrawal credentials
// if has_compounding_withdrawal_credential(validator) and has_sufficient_effective_balance and has_excess_balance:
// to_withdraw = min(
// state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw,
// amount
// )
// exit_queue_epoch = compute_exit_epoch_and_update_churn(state, to_withdraw)
// withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
// state.pending_partial_withdrawals.append(PendingPartialWithdrawal(
// index=index,
// amount=to_withdraw,
// withdrawable_epoch=withdrawable_epoch,
// ))
// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
// has_excess_balance = state.balances[index] > MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw
//
// # Only allow partial withdrawals with compounding withdrawal credentials
// if has_compounding_withdrawal_credential(validator) and has_sufficient_effective_balance and has_excess_balance:
// to_withdraw = min(
// state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw,
// amount
// )
// exit_queue_epoch = compute_exit_epoch_and_update_churn(state, to_withdraw)
// withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
// state.pending_partial_withdrawals.append(PendingPartialWithdrawal(
// index=index,
// amount=to_withdraw,
// withdrawable_epoch=withdrawable_epoch,
// ))
func ProcessWithdrawalRequests(ctx context.Context, st state.BeaconState, wrs []*enginev1.WithdrawalRequest) (state.BeaconState, error) {
ctx, span := trace.StartSpan(ctx, "electra.ProcessWithdrawalRequests")
defer span.End()

View File

@@ -79,6 +79,7 @@ go_test(
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",

View File

@@ -584,13 +584,13 @@ func IsSameWithdrawalCredentials(a, b *ethpb.Validator) bool {
// and validator.withdrawable_epoch <= epoch
// and balance > 0
// )
func IsFullyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch) bool {
func IsFullyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch, fork int) bool {
if val == nil || balance <= 0 {
return false
}
// Electra / EIP-7251 logic
if epoch >= params.BeaconConfig().ElectraForkEpoch {
if fork >= version.Electra {
return HasExecutionWithdrawalCredentials(val) && val.WithdrawableEpoch <= epoch
}
@@ -600,12 +600,12 @@ func IsFullyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch pr
// IsPartiallyWithdrawableValidator returns whether the validator is able to perform a
// partial withdrawal. This function assumes that the caller has a lock on the state.
// This method conditionally calls the fork appropriate implementation based on the epoch argument.
func IsPartiallyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch) bool {
func IsPartiallyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch, fork int) bool {
if val == nil {
return false
}
if epoch < params.BeaconConfig().ElectraForkEpoch {
if fork < version.Electra {
return isPartiallyWithdrawableValidatorCapella(val, balance, epoch)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
@@ -970,6 +971,7 @@ func TestIsFullyWithdrawableValidator(t *testing.T) {
validator *ethpb.Validator
balance uint64
epoch primitives.Epoch
fork int
want bool
}{
{
@@ -1027,13 +1029,14 @@ func TestIsFullyWithdrawableValidator(t *testing.T) {
},
balance: params.BeaconConfig().MaxEffectiveBalance,
epoch: params.BeaconConfig().ElectraForkEpoch,
fork: version.Electra,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, helpers.IsFullyWithdrawableValidator(tt.validator, tt.balance, tt.epoch))
assert.Equal(t, tt.want, helpers.IsFullyWithdrawableValidator(tt.validator, tt.balance, tt.epoch, tt.fork))
})
}
}
@@ -1044,6 +1047,7 @@ func TestIsPartiallyWithdrawableValidator(t *testing.T) {
validator *ethpb.Validator
balance uint64
epoch primitives.Epoch
fork int
want bool
}{
{
@@ -1091,6 +1095,7 @@ func TestIsPartiallyWithdrawableValidator(t *testing.T) {
},
balance: params.BeaconConfig().MinActivationBalance * 2,
epoch: params.BeaconConfig().ElectraForkEpoch,
fork: version.Electra,
want: true,
},
{
@@ -1101,13 +1106,14 @@ func TestIsPartiallyWithdrawableValidator(t *testing.T) {
},
balance: params.BeaconConfig().MaxEffectiveBalanceElectra * 2,
epoch: params.BeaconConfig().ElectraForkEpoch,
fork: version.Electra,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, helpers.IsPartiallyWithdrawableValidator(tt.validator, tt.balance, tt.epoch))
assert.Equal(t, tt.want, helpers.IsPartiallyWithdrawableValidator(tt.validator, tt.balance, tt.epoch, tt.fork))
})
}
}

View File

@@ -69,7 +69,7 @@ go_test(
],
data = [
"//testing/benchmark/benchmark_files:benchmark_data",
],
] + glob(["testdata/**"]),
embed = [":go_default_library"],
shard_count = 3,
deps = [
@@ -84,6 +84,7 @@ go_test(
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
@@ -97,8 +98,11 @@ go_test(
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//time/slots:go_default_library",
"@com_github_golang_snappy//:go_default_library",
"@com_github_google_go_cmp//cmp:go_default_library",
"@com_github_google_gofuzz//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//testing/protocmp:go_default_library",
],
)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
{blocks_count: 2, bls_setting: 1}

Binary file not shown.

Binary file not shown.

View File

@@ -356,11 +356,15 @@ func VerifyOperationLengths(_ context.Context, state state.BeaconState, b interf
)
}
if uint64(len(body.Attestations())) > params.BeaconConfig().MaxAttestations {
maxAtts := params.BeaconConfig().MaxAttestationsElectra
if state.Version() < version.Electra {
maxAtts = params.BeaconConfig().MaxAttestations
}
if uint64(len(body.Attestations())) > maxAtts {
return nil, fmt.Errorf(
"number of attestations (%d) in block body exceeds allowed threshold of %d",
len(body.Attestations()),
params.BeaconConfig().MaxAttestations,
maxAtts,
)
}

View File

@@ -3,8 +3,11 @@ package transition_test
import (
"context"
"fmt"
"os"
"testing"
"github.com/golang/snappy"
"github.com/google/go-cmp/cmp"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
@@ -16,6 +19,7 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
@@ -25,6 +29,8 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
)
func init() {
@@ -112,6 +118,55 @@ func TestExecuteStateTransition_FullProcess(t *testing.T) {
assert.DeepNotEqual(t, oldMix, mix, "Did not expect new and old randao mix to equal")
}
func TestExecuteStateTransition_SpecTestRandom0TestCauseDebugging(t *testing.T) {
var blks []interfaces.ReadOnlySignedBeaconBlock
for _, fp := range []string{
"testdata/blocks_0.ssz_snappy",
"testdata/blocks_1.ssz_snappy",
} {
b, err := os.ReadFile(fp)
require.NoError(t, err)
decoded, err := snappy.Decode(nil, b)
require.NoError(t, err)
block := &ethpb.SignedBeaconBlockElectra{}
require.NoError(t, block.UnmarshalSSZ(decoded))
wsb, err := consensusblocks.NewSignedBeaconBlock(block)
require.NoError(t, err)
blks = append(blks, wsb)
}
var states []state.BeaconState
for _, fp := range []string{
"testdata/pre.ssz_snappy",
"testdata/post.ssz_snappy",
} {
base := &ethpb.BeaconStateElectra{}
b, err := os.ReadFile(fp)
require.NoError(t, err)
decoded, err := snappy.Decode(nil, b)
require.NoError(t, err)
require.NoError(t, base.UnmarshalSSZ(decoded))
state, err := state_native.InitializeFromProtoElectra(base)
require.NoError(t, err)
states = append(states, state)
}
s := states[0]
for _, wsb := range blks {
st, err := transition.ExecuteStateTransition(context.Background(), s, wsb)
if err != nil {
break
}
s = st
}
pre, post := s.ToProtoUnsafe().(proto.Message), states[1].ToProtoUnsafe().(proto.Message)
if !proto.Equal(pre, post) {
t.Log(cmp.Diff(pre, post, protocmp.Transform()))
}
}
func TestProcessBlock_IncorrectProcessExits(t *testing.T) {
beaconState, _ := util.DeterministicGenesisState(t, 100)

View File

@@ -359,7 +359,7 @@ func (bs *Server) AttestationPoolElectra(_ context.Context, req *ethpb.Attestati
}
func blockAttestations[T ethpb.Att](blocks []interfaces.ReadOnlySignedBeaconBlock) ([]T, error) {
blockAtts := make([]ethpb.Att, 0, params.BeaconConfig().MaxAttestations*uint64(len(blocks)))
blockAtts := make([]ethpb.Att, 0, params.BeaconConfig().MaxAttestations*uint64(len(blocks))) // TODO: Electra
for _, blk := range blocks {
blockAtts = append(blockAtts, blk.Block().Body().Attestations()...)
}
@@ -388,7 +388,7 @@ func blockIndexedAttestations[T ethpb.IndexedAtt](
blocks []interfaces.ReadOnlySignedBeaconBlock,
stateGen stategen.StateManager,
) ([]T, error) {
attsArray := make([]ethpb.Att, 0, params.BeaconConfig().MaxAttestations*uint64(len(blocks)))
attsArray := make([]ethpb.Att, 0, params.BeaconConfig().MaxAttestations*uint64(len(blocks))) // TODO: Electra
for _, b := range blocks {
attsArray = append(attsArray, b.Block().Body().Attestations()...)
}

View File

@@ -154,7 +154,7 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
if err != nil {
return nil, 0, errors.Wrapf(err, "could not retrieve balance at index %d", validatorIndex)
}
if helpers.IsFullyWithdrawableValidator(val, balance, epoch) {
if helpers.IsFullyWithdrawableValidator(val, balance, epoch, b.version) {
withdrawals = append(withdrawals, &enginev1.Withdrawal{
Index: withdrawalIndex,
ValidatorIndex: validatorIndex,
@@ -162,7 +162,7 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
Amount: balance,
})
withdrawalIndex++
} else if helpers.IsPartiallyWithdrawableValidator(val, balance, epoch) {
} else if helpers.IsPartiallyWithdrawableValidator(val, balance, epoch, b.version) {
withdrawals = append(withdrawals, &enginev1.Withdrawal{
Index: withdrawalIndex,
ValidatorIndex: validatorIndex,

View File

@@ -54,7 +54,7 @@ func (b *BeaconState) AppendCurrentEpochAttestations(val *ethpb.PendingAttestati
}
atts := b.currentEpochAttestations
max := params.BeaconConfig().CurrentEpochAttestationsLength()
max := params.BeaconConfig().CurrentEpochAttestationsLength(b.version)
if uint64(len(atts)) >= max {
return fmt.Errorf("current pending attestation exceeds max length %d", max)
}
@@ -84,7 +84,7 @@ func (b *BeaconState) AppendPreviousEpochAttestations(val *ethpb.PendingAttestat
}
atts := b.previousEpochAttestations
max := params.BeaconConfig().PreviousEpochAttestationsLength()
max := params.BeaconConfig().PreviousEpochAttestationsLength(b.version)
if uint64(len(atts)) >= max {
return fmt.Errorf("previous pending attestation exceeds max length %d", max)
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
@@ -79,7 +80,7 @@ func BenchmarkAppendPreviousEpochAttestations(b *testing.B) {
st, err := InitializeFromProtoPhase0(&ethpb.BeaconState{})
require.NoError(b, err)
max := params.BeaconConfig().PreviousEpochAttestationsLength()
max := params.BeaconConfig().PreviousEpochAttestationsLength(version.Phase0)
if max < 2 {
b.Fatalf("previous epoch attestations length is less than 2: %d", max)
}

View File

@@ -1227,7 +1227,7 @@ func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex)
err := b.resetFieldTrie(
field,
b.previousEpochAttestations,
params.BeaconConfig().PreviousEpochAttestationsLength(),
params.BeaconConfig().PreviousEpochAttestationsLength(b.version),
)
if err != nil {
return [32]byte{}, err
@@ -1241,7 +1241,7 @@ func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex)
err := b.resetFieldTrie(
field,
b.currentEpochAttestations,
params.BeaconConfig().CurrentEpochAttestationsLength(),
params.BeaconConfig().CurrentEpochAttestationsLength(b.version),
)
if err != nil {
return [32]byte{}, err

View File

@@ -38,6 +38,7 @@ go_library(
"//encoding/ssz:go_default_library",
"//math:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],

View File

@@ -9,6 +9,7 @@ import (
params "github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
// RootsArrayHashTreeRoot computes the Merkle root of arrays of 32-byte hashes, such as [64][32]byte
@@ -18,7 +19,7 @@ func RootsArrayHashTreeRoot(vals [][]byte, length uint64) ([32]byte, error) {
}
func EpochAttestationsRoot(atts []*ethpb.PendingAttestation) ([32]byte, error) {
max := params.BeaconConfig().CurrentEpochAttestationsLength()
max := params.BeaconConfig().CurrentEpochAttestationsLength(version.Phase0)
if uint64(len(atts)) > max {
return [32]byte{}, fmt.Errorf("epoch attestation exceeds max length %d", max)
}
@@ -32,7 +33,7 @@ func EpochAttestationsRoot(atts []*ethpb.PendingAttestation) ([32]byte, error) {
roots[i] = pendingRoot
}
attsRootsRoot, err := ssz.BitwiseMerkleize(roots, uint64(len(roots)), params.BeaconConfig().CurrentEpochAttestationsLength())
attsRootsRoot, err := ssz.BitwiseMerkleize(roots, uint64(len(roots)), params.BeaconConfig().CurrentEpochAttestationsLength(version.Phase0))
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not compute epoch attestations merkleization")
}

View File

@@ -403,6 +403,26 @@ func TestSidecarInclusionProven(t *testing.T) {
require.NotNil(t, v.results.result(RequireSidecarInclusionProven))
}
func TestSidecarInclusionProvenElectra(t *testing.T) {
// GenerateTestDenebBlockWithSidecar is supposed to generate valid inclusion proofs
_, blobs := util.GenerateTestElectraBlockWithSidecar(t, [32]byte{}, 1, 1)
b := blobs[0]
ini := Initializer{}
v := ini.NewBlobVerifier(b, GossipSidecarRequirements)
require.NoError(t, v.SidecarInclusionProven())
require.Equal(t, true, v.results.executed(RequireSidecarInclusionProven))
require.NoError(t, v.results.result(RequireSidecarInclusionProven))
// Invert bits of the first byte of the body root to mess up the proof
byte0 := b.SignedBlockHeader.Header.BodyRoot[0]
b.SignedBlockHeader.Header.BodyRoot[0] = byte0 ^ 255
v = ini.NewBlobVerifier(b, GossipSidecarRequirements)
require.ErrorIs(t, v.SidecarInclusionProven(), ErrSidecarInclusionProofInvalid)
require.Equal(t, true, v.results.executed(RequireSidecarInclusionProven))
require.NotNil(t, v.results.result(RequireSidecarInclusionProven))
}
func TestSidecarKzgProofVerified(t *testing.T) {
// GenerateTestDenebBlockWithSidecar is supposed to generate valid commitments
_, blobs := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, 1, 1)

View File

@@ -325,15 +325,21 @@ func (b *BeaconChainConfig) Eth1DataVotesLength() uint64 {
// PreviousEpochAttestationsLength returns the maximum length of the pending
// attestation list for the previous epoch, computed from the parameters in
// BeaconChainConfig.
func (b *BeaconChainConfig) PreviousEpochAttestationsLength() uint64 {
return uint64(b.SlotsPerEpoch.Mul(b.MaxAttestations))
func (b *BeaconChainConfig) PreviousEpochAttestationsLength(fork int) uint64 {
if fork < version.Electra {
return uint64(b.SlotsPerEpoch.Mul(b.MaxAttestations))
}
return uint64(b.SlotsPerEpoch.Mul(b.MaxAttestationsElectra))
}
// CurrentEpochAttestationsLength returns the maximum length of the pending
// attestation list for the current epoch, computed from the parameters in
// BeaconChainConfig.
func (b *BeaconChainConfig) CurrentEpochAttestationsLength() uint64 {
return uint64(b.SlotsPerEpoch.Mul(b.MaxAttestations))
func (b *BeaconChainConfig) CurrentEpochAttestationsLength(fork int) uint64 {
if fork < version.Electra {
return uint64(b.SlotsPerEpoch.Mul(b.MaxAttestations))
}
return uint64(b.SlotsPerEpoch.Mul(b.MaxAttestationsElectra))
}
// TtfbTimeoutDuration returns the time duration of the timeout.

View File

@@ -156,7 +156,12 @@ func topLevelRoots(body interfaces.ReadOnlyBeaconBlockBody) ([][]byte, error) {
// Attester slashings
as := body.AttesterSlashings()
root, err = ssz.MerkleizeListSSZ(as, params.BeaconConfig().MaxAttesterSlashings)
bodyVersion := body.Version()
if bodyVersion < version.Electra {
root, err = ssz.MerkleizeListSSZ(as, params.BeaconConfig().MaxAttesterSlashings)
} else {
root, err = ssz.MerkleizeListSSZ(as, params.BeaconConfig().MaxAttesterSlashingsElectra)
}
if err != nil {
return nil, err
}
@@ -164,7 +169,11 @@ func topLevelRoots(body interfaces.ReadOnlyBeaconBlockBody) ([][]byte, error) {
// Attestations
att := body.Attestations()
root, err = ssz.MerkleizeListSSZ(att, params.BeaconConfig().MaxAttestations)
if bodyVersion < version.Electra {
root, err = ssz.MerkleizeListSSZ(att, params.BeaconConfig().MaxAttestations)
} else {
root, err = ssz.MerkleizeListSSZ(att, params.BeaconConfig().MaxAttestationsElectra)
}
if err != nil {
return nil, err
}

View File

@@ -7,6 +7,5 @@ import (
)
func TestMainnet_Electra_Transition(t *testing.T) {
t.Skip("TODO: Electra")
fork.RunForkTransitionTest(t, "mainnet")
}

View File

@@ -8,6 +8,5 @@ import (
)
func TestMainnet_Electra_Forkchoice(t *testing.T) {
t.Skip("TODO: Electra")
forkchoice.Run(t, "mainnet", version.Electra)
}

View File

@@ -7,6 +7,5 @@ import (
)
func TestMainnet_Electra_Random(t *testing.T) {
t.Skip("TODO: Electra")
sanity.RunBlockProcessingTest(t, "mainnet", "random/random/pyspec_tests")
}

View File

@@ -7,6 +7,5 @@ import (
)
func TestMinimal_Electra_Transition(t *testing.T) {
t.Skip("TODO: Electra")
fork.RunForkTransitionTest(t, "minimal")
}

View File

@@ -8,6 +8,5 @@ import (
)
func TestMinimal_Electra_Forkchoice(t *testing.T) {
t.Skip("TODO: Electra")
forkchoice.Run(t, "minimal", version.Electra)
}

View File

@@ -7,6 +7,5 @@ import (
)
func TestMinimal_Electra_Random(t *testing.T) {
t.Skip("TODO: Electra")
sanity.RunBlockProcessingTest(t, "minimal", "random/random/pyspec_tests")
}

View File

@@ -7,6 +7,5 @@ import (
)
func TestMinimal_Electra_Sanity_Blocks(t *testing.T) {
t.Skip("TODO: Electra")
sanity.RunBlockProcessingTest(t, "minimal", "sanity/blocks/pyspec_tests")
}

View File

@@ -120,7 +120,7 @@ func (bb *Builder) PoWBlock(pb *ethpb.PowBlock) {
}
// Attestation receives the attestation and updates forkchoice.
func (bb *Builder) Attestation(t testing.TB, a *ethpb.Attestation) {
func (bb *Builder) Attestation(t testing.TB, a ethpb.Att) {
require.NoError(t, bb.service.OnAttestation(context.TODO(), a, params.BeaconConfig().MaximumGossipClockDisparityDuration()))
}

View File

@@ -37,7 +37,7 @@ func Run(t *testing.T, config string, fork int) {
}
}
func runTest(t *testing.T, config string, fork int, basePath string) {
func runTest(t *testing.T, config string, fork int, basePath string) { // nolint:gocognit
require.NoError(t, utils.SetConfig(t, config))
testFolders, _ := utils.TestFolders(t, config, version.String(fork), basePath)
if len(testFolders) == 0 {
@@ -144,7 +144,12 @@ func runTest(t *testing.T, config string, fork int, basePath string) {
require.NoError(t, err)
attSSZ, err := snappy.Decode(nil /* dst */, attFile)
require.NoError(t, err)
att := &ethpb.Attestation{}
var att ethpb.Att
if fork < version.Electra {
att = &ethpb.Attestation{}
} else {
att = &ethpb.AttestationElectra{}
}
require.NoError(t, att.UnmarshalSSZ(attSSZ), "Failed to unmarshal")
builder.Attestation(t, att)
}