mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-02 17:15:21 -05:00
Compare commits
1 Commits
close-test
...
gloas/proc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f75c3082d5 |
@@ -7,6 +7,7 @@ go_library(
|
||||
"payload_attestation.go",
|
||||
"pending_payment.go",
|
||||
"proposer_slashing.go",
|
||||
"withdrawal.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
107
beacon-chain/core/gloas/withdrawal.go
Normal file
107
beacon-chain/core/gloas/withdrawal.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProcessWithdrawals applies withdrawals to the state for Gloas.
|
||||
//
|
||||
// Spec v1.7.0-alpha.1 (pseudocode):
|
||||
//
|
||||
// def process_withdrawals(
|
||||
//
|
||||
// state: BeaconState,
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `payload`
|
||||
//
|
||||
// ) -> None:
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Return early if the parent block is empty
|
||||
// if not is_parent_block_full(state):
|
||||
// return
|
||||
//
|
||||
// # Get expected withdrawals
|
||||
// expected = get_expected_withdrawals(state)
|
||||
//
|
||||
// # Apply expected withdrawals
|
||||
// apply_withdrawals(state, expected.withdrawals)
|
||||
//
|
||||
// # Update withdrawals fields in the state
|
||||
// update_next_withdrawal_index(state, expected.withdrawals)
|
||||
// # [New in Gloas:EIP7732]
|
||||
// update_payload_expected_withdrawals(state, expected.withdrawals)
|
||||
// # [New in Gloas:EIP7732]
|
||||
// update_builder_pending_withdrawals(state, expected.processed_builder_withdrawals_count)
|
||||
// update_pending_partial_withdrawals(state, expected.processed_partial_withdrawals_count)
|
||||
// # [New in Gloas:EIP7732]
|
||||
// update_next_withdrawal_builder_index(state, expected.processed_builders_sweep_count)
|
||||
// update_next_withdrawal_validator_index(state, expected.withdrawals)
|
||||
func ProcessWithdrawals(st state.BeaconState) error {
|
||||
full, err := st.IsParentBlockFull()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get parent block full status")
|
||||
}
|
||||
if !full {
|
||||
return nil
|
||||
}
|
||||
|
||||
expected, err := st.ExpectedWithdrawalsGloas()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get expected withdrawals")
|
||||
}
|
||||
|
||||
if err := st.DecreaseWithdrawalBalances(expected.Withdrawals); err != nil {
|
||||
return errors.Wrap(err, "could not decrease withdrawal balances")
|
||||
}
|
||||
|
||||
if len(expected.Withdrawals) > 0 {
|
||||
if err := st.SetNextWithdrawalIndex(expected.Withdrawals[len(expected.Withdrawals)-1].Index + 1); err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal index")
|
||||
}
|
||||
}
|
||||
|
||||
err = st.SetPayloadExpectedWithdrawals(expected.Withdrawals)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not set payload expected withdrawals")
|
||||
}
|
||||
|
||||
err = st.DequeueBuilderPendingWithdrawals(expected.ProcessedBuilderWithdrawalsCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to dequeue builder pending withdrawals from state: %w", err)
|
||||
}
|
||||
|
||||
if err := st.DequeuePendingPartialWithdrawals(expected.ProcessedPartialWithdrawalsCount); err != nil {
|
||||
return fmt.Errorf("unable to dequeue partial withdrawals from state: %w", err)
|
||||
}
|
||||
|
||||
err = st.SetNextWithdrawalBuilderIndex(expected.NextWithdrawalBuilderIndex)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal builder index")
|
||||
}
|
||||
|
||||
var nextValidatorIndex primitives.ValidatorIndex
|
||||
if uint64(len(expected.Withdrawals)) < params.BeaconConfig().MaxWithdrawalsPerPayload {
|
||||
nextValidatorIndex, err = st.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get next withdrawal validator index")
|
||||
}
|
||||
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
|
||||
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
|
||||
} else {
|
||||
nextValidatorIndex = expected.Withdrawals[len(expected.Withdrawals)-1].ValidatorIndex + 1
|
||||
if nextValidatorIndex == primitives.ValidatorIndex(st.NumValidators()) {
|
||||
nextValidatorIndex = 0
|
||||
}
|
||||
}
|
||||
if err := st.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal validator index")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -84,6 +84,8 @@ func TestGetSpec(t *testing.T) {
|
||||
config.FuluForkVersion = []byte("FuluForkVersion")
|
||||
config.FuluForkEpoch = 109
|
||||
config.GloasForkEpoch = 110
|
||||
config.BuilderIndexFlag = 111
|
||||
config.MaxBuildersPerWithdrawalsSweep = 112
|
||||
config.BLSWithdrawalPrefixByte = byte('b')
|
||||
config.ETH1AddressWithdrawalPrefixByte = byte('c')
|
||||
config.GenesisDelay = 24
|
||||
@@ -220,7 +222,7 @@ func TestGetSpec(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
|
||||
data, ok := resp.Data.(map[string]any)
|
||||
require.Equal(t, true, ok)
|
||||
assert.Equal(t, 192, len(data))
|
||||
assert.Equal(t, 194, len(data))
|
||||
for k, v := range data {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
switch k {
|
||||
@@ -302,6 +304,10 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "109", v)
|
||||
case "GLOAS_FORK_EPOCH":
|
||||
assert.Equal(t, "110", v)
|
||||
case "BUILDER_INDEX_FLAG":
|
||||
assert.Equal(t, "111", v)
|
||||
case "MAX_BUILDERS_PER_WITHDRAWALS_SWEEP":
|
||||
assert.Equal(t, "112", v)
|
||||
case "MIN_ANCHOR_POW_BLOCK_DIFFICULTY":
|
||||
assert.Equal(t, "1000", v)
|
||||
case "BLS_WITHDRAWAL_PREFIX":
|
||||
|
||||
@@ -3,6 +3,7 @@ package state
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -13,6 +14,10 @@ type writeOnlyGloasFields interface {
|
||||
RotateBuilderPendingPayments() error
|
||||
AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal) error
|
||||
UpdateExecutionPayloadAvailabilityAtIndex(idx uint64, val byte) error
|
||||
SetPayloadExpectedWithdrawals(withdrawals []*enginev1.Withdrawal) error
|
||||
DecreaseWithdrawalBalances(withdrawals []*enginev1.Withdrawal) error
|
||||
DequeueBuilderPendingWithdrawals(num uint64) error
|
||||
SetNextWithdrawalBuilderIndex(idx primitives.BuilderIndex) error
|
||||
}
|
||||
|
||||
type readOnlyGloasFields interface {
|
||||
@@ -21,4 +26,15 @@ type readOnlyGloasFields interface {
|
||||
CanBuilderCoverBid(primitives.BuilderIndex, primitives.Gwei) (bool, error)
|
||||
LatestBlockHash() ([32]byte, error)
|
||||
BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment, error)
|
||||
IsParentBlockFull() (bool, error)
|
||||
ExpectedWithdrawalsGloas() (ExpectedWithdrawalsGloasResult, error)
|
||||
}
|
||||
|
||||
// ExpectedWithdrawalsGloasResult bundles the expected withdrawals and related counters
|
||||
// for the Gloas fork to avoid positional return mistakes.
|
||||
type ExpectedWithdrawalsGloasResult struct {
|
||||
Withdrawals []*enginev1.Withdrawal
|
||||
ProcessedBuilderWithdrawalsCount uint64
|
||||
ProcessedPartialWithdrawalsCount uint64
|
||||
NextWithdrawalBuilderIndex primitives.BuilderIndex
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package state_native
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
)
|
||||
|
||||
// LatestBlockHash returns the hash of the latest execution block.
|
||||
@@ -147,3 +151,223 @@ func (b *BeaconState) BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment,
|
||||
|
||||
return b.builderPendingPaymentsVal(), nil
|
||||
}
|
||||
|
||||
// IsParentBlockFull returns true if the last committed payload bid was fulfilled with a payload,
|
||||
// which can only happen when both beacon block and payload were present.
|
||||
// This function must be called on a beacon state before processing the execution payload bid in the block.
|
||||
// Spec v1.7.0-alpha.2 (pseudocode):
|
||||
// def is_parent_block_full(state: BeaconState) -> bool:
|
||||
//
|
||||
// return state.latest_execution_payload_bid.block_hash == state.latest_block_hash
|
||||
func (b *BeaconState) IsParentBlockFull() (bool, error) {
|
||||
if b.version < version.Gloas {
|
||||
return false, errNotSupported("IsParentBlockFull", b.version)
|
||||
}
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.latestExecutionPayloadBid == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return bytes.Equal(b.latestExecutionPayloadBid.BlockHash, b.latestBlockHash), nil
|
||||
}
|
||||
|
||||
// ExpectedWithdrawalsGloas returns the withdrawals that a proposer will need to pack in the next block
|
||||
// applied to the current state. It is also used by validators to check that the execution payload carried
|
||||
// the right number of withdrawals.
|
||||
//
|
||||
// Spec v1.7.0-alpha.1:
|
||||
//
|
||||
// def get_expected_withdrawals(state: BeaconState) -> ExpectedWithdrawals:
|
||||
// withdrawal_index = state.next_withdrawal_index
|
||||
// withdrawals: List[Withdrawal] = []
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Get builder withdrawals
|
||||
// builder_withdrawals, withdrawal_index, processed_builder_withdrawals_count = (
|
||||
// get_builder_withdrawals(state, withdrawal_index, withdrawals)
|
||||
// )
|
||||
// withdrawals.extend(builder_withdrawals)
|
||||
//
|
||||
// # Get partial withdrawals
|
||||
// partial_withdrawals, withdrawal_index, processed_partial_withdrawals_count = (
|
||||
// get_pending_partial_withdrawals(state, withdrawal_index, withdrawals)
|
||||
// )
|
||||
// withdrawals.extend(partial_withdrawals)
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Get builders sweep withdrawals
|
||||
// builders_sweep_withdrawals, withdrawal_index, processed_builders_sweep_count = (
|
||||
// get_builders_sweep_withdrawals(state, withdrawal_index, withdrawals)
|
||||
// )
|
||||
// withdrawals.extend(builders_sweep_withdrawals)
|
||||
//
|
||||
// # Get validators sweep withdrawals
|
||||
// validators_sweep_withdrawals, withdrawal_index, processed_validators_sweep_count = (
|
||||
// get_validators_sweep_withdrawals(state, withdrawal_index, withdrawals)
|
||||
// )
|
||||
// withdrawals.extend(validators_sweep_withdrawals)
|
||||
//
|
||||
// return ExpectedWithdrawals(
|
||||
// withdrawals,
|
||||
// # [New in Gloas:EIP7732]
|
||||
// processed_builder_withdrawals_count,
|
||||
// processed_partial_withdrawals_count,
|
||||
// # [New in Gloas:EIP7732]
|
||||
// processed_builders_sweep_count,
|
||||
// processed_validators_sweep_count,
|
||||
// )
|
||||
func (b *BeaconState) ExpectedWithdrawalsGloas() (state.ExpectedWithdrawalsGloasResult, error) {
|
||||
if b.version < version.Gloas {
|
||||
return state.ExpectedWithdrawalsGloasResult{}, errNotSupported("ExpectedWithdrawalsGloas", b.version)
|
||||
}
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
withdrawals := make([]*enginev1.Withdrawal, 0, cfg.MaxWithdrawalsPerPayload)
|
||||
withdrawalIndex := b.nextWithdrawalIndex
|
||||
|
||||
withdrawalIndex, processedBuilderWithdrawalsCount, err := b.appendBuilderWithdrawals(withdrawalIndex, &withdrawals)
|
||||
if err != nil {
|
||||
return state.ExpectedWithdrawalsGloasResult{}, err
|
||||
}
|
||||
|
||||
withdrawalIndex, processedPartialWithdrawalsCount, err := b.appendPendingPartialWithdrawals(withdrawalIndex, &withdrawals)
|
||||
if err != nil {
|
||||
return state.ExpectedWithdrawalsGloasResult{}, err
|
||||
}
|
||||
|
||||
withdrawalIndex, nextBuilderIndex, err := b.appendBuildersSweepWithdrawals(withdrawalIndex, &withdrawals)
|
||||
if err != nil {
|
||||
return state.ExpectedWithdrawalsGloasResult{}, err
|
||||
}
|
||||
|
||||
err = b.appendValidatorsSweepWithdrawals(withdrawalIndex, &withdrawals)
|
||||
if err != nil {
|
||||
return state.ExpectedWithdrawalsGloasResult{}, err
|
||||
}
|
||||
|
||||
return state.ExpectedWithdrawalsGloasResult{
|
||||
Withdrawals: withdrawals,
|
||||
ProcessedBuilderWithdrawalsCount: processedBuilderWithdrawalsCount,
|
||||
ProcessedPartialWithdrawalsCount: processedPartialWithdrawalsCount,
|
||||
NextWithdrawalBuilderIndex: nextBuilderIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// appendBuilderWithdrawals returns builder pending withdrawals, the updated withdrawal index,
|
||||
// and the processed count, following spec v1.7.0-alpha.2:
|
||||
//
|
||||
// def get_builder_withdrawals(state, withdrawal_index, prior_withdrawals):
|
||||
// withdrawals_limit = MAX_WITHDRAWALS_PER_PAYLOAD - 1
|
||||
// assert len(prior_withdrawals) <= withdrawals_limit
|
||||
// processed_count = 0
|
||||
// withdrawals = []
|
||||
// for withdrawal in state.builder_pending_withdrawals:
|
||||
// if len(prior_withdrawals + withdrawals) >= withdrawals_limit:
|
||||
// break
|
||||
// withdrawals.append(Withdrawal(
|
||||
// index=withdrawal_index,
|
||||
// validator_index=convert_builder_index_to_validator_index(withdrawal.builder_index),
|
||||
// address=withdrawal.fee_recipient,
|
||||
// amount=withdrawal.amount,
|
||||
// ))
|
||||
// withdrawal_index += 1
|
||||
// processed_count += 1
|
||||
// return withdrawals, withdrawal_index, processed_count
|
||||
func (b *BeaconState) appendBuilderWithdrawals(withdrawalIndex uint64, withdrawals *[]*enginev1.Withdrawal) (uint64, uint64, error) {
|
||||
cfg := params.BeaconConfig()
|
||||
withdrawalsLimit := int(cfg.MaxWithdrawalsPerPayload - 1)
|
||||
ws := *withdrawals
|
||||
if len(ws) > withdrawalsLimit {
|
||||
return withdrawalIndex, 0, fmt.Errorf("prior withdrawals length %d exceeds limit %d", len(ws), withdrawalsLimit)
|
||||
}
|
||||
|
||||
var processedCount uint64
|
||||
for _, w := range b.builderPendingWithdrawals {
|
||||
if len(ws) >= withdrawalsLimit {
|
||||
break
|
||||
}
|
||||
|
||||
ws = append(ws, &enginev1.Withdrawal{
|
||||
Index: withdrawalIndex,
|
||||
ValidatorIndex: w.BuilderIndex.ToValidatorIndex(),
|
||||
Address: w.FeeRecipient,
|
||||
Amount: uint64(w.Amount),
|
||||
})
|
||||
withdrawalIndex++
|
||||
processedCount++
|
||||
}
|
||||
|
||||
*withdrawals = ws
|
||||
return withdrawalIndex, processedCount, nil
|
||||
}
|
||||
|
||||
// appendBuildersSweepWithdrawals returns builder sweep withdrawals, the updated withdrawal index,
|
||||
// and the processed count, following spec v1.7.0-alpha.2:
|
||||
//
|
||||
// def get_builders_sweep_withdrawals(state, withdrawal_index, prior_withdrawals):
|
||||
// epoch = get_current_epoch(state)
|
||||
// builders_limit = min(len(state.builders), MAX_BUILDERS_PER_WITHDRAWALS_SWEEP)
|
||||
// withdrawals_limit = MAX_WITHDRAWALS_PER_PAYLOAD - 1
|
||||
// assert len(prior_withdrawals) <= withdrawals_limit
|
||||
// processed_count = 0
|
||||
// withdrawals = []
|
||||
// builder_index = state.next_withdrawal_builder_index
|
||||
// for _ in range(builders_limit):
|
||||
// if len(prior_withdrawals + withdrawals) >= withdrawals_limit:
|
||||
// break
|
||||
// builder = state.builders[builder_index]
|
||||
// if builder.withdrawable_epoch <= epoch and builder.balance > 0:
|
||||
// withdrawals.append(Withdrawal(
|
||||
// index=withdrawal_index,
|
||||
// validator_index=convert_builder_index_to_validator_index(builder_index),
|
||||
// address=builder.execution_address,
|
||||
// amount=builder.balance,
|
||||
// ))
|
||||
// withdrawal_index += 1
|
||||
// builder_index = BuilderIndex((builder_index + 1) % len(state.builders))
|
||||
// processed_count += 1
|
||||
// return withdrawals, withdrawal_index, processed_count
|
||||
func (b *BeaconState) appendBuildersSweepWithdrawals(withdrawalIndex uint64, withdrawals *[]*enginev1.Withdrawal) (uint64, primitives.BuilderIndex, error) {
|
||||
cfg := params.BeaconConfig()
|
||||
withdrawalsLimit := int(cfg.MaxWithdrawalsPerPayload - 1)
|
||||
if len(*withdrawals) > withdrawalsLimit {
|
||||
return withdrawalIndex, 0, fmt.Errorf("prior withdrawals length %d exceeds limit %d", len(*withdrawals), withdrawalsLimit)
|
||||
}
|
||||
|
||||
ws := *withdrawals
|
||||
|
||||
buildersLimit := len(b.builders)
|
||||
if maxBuilders := int(cfg.MaxBuildersPerWithdrawalsSweep); buildersLimit > maxBuilders {
|
||||
buildersLimit = maxBuilders
|
||||
}
|
||||
|
||||
builderIndex := b.nextWithdrawalBuilderIndex
|
||||
epoch := slots.ToEpoch(b.slot)
|
||||
for i := 0; i < buildersLimit; i++ {
|
||||
if len(ws) >= withdrawalsLimit {
|
||||
break
|
||||
}
|
||||
|
||||
builder := b.builders[builderIndex]
|
||||
if builder != nil && builder.WithdrawableEpoch <= epoch && builder.Balance > 0 {
|
||||
ws = append(ws, &enginev1.Withdrawal{
|
||||
Index: withdrawalIndex,
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Address: builder.ExecutionAddress,
|
||||
Amount: uint64(builder.Balance),
|
||||
})
|
||||
withdrawalIndex++
|
||||
}
|
||||
|
||||
builderIndex = primitives.BuilderIndex((uint64(builderIndex) + 1) % uint64(len(b.builders)))
|
||||
}
|
||||
|
||||
*withdrawals = ws
|
||||
return withdrawalIndex, builderIndex, nil
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
package state_native_test
|
||||
package state_native
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
)
|
||||
|
||||
func TestLatestBlockHash(t *testing.T) {
|
||||
t.Run("returns error before gloas", func(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
_, err := st.LatestBlockHash()
|
||||
require.ErrorContains(t, "is not supported", err)
|
||||
})
|
||||
|
||||
t.Run("returns zero hash when unset", func(t *testing.T) {
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{})
|
||||
st, err := InitializeFromProtoGloas(ðpb.BeaconStateGloas{})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := st.LatestBlockHash()
|
||||
@@ -33,7 +34,7 @@ func TestLatestBlockHash(t *testing.T) {
|
||||
var want [32]byte
|
||||
copy(want[:], hashBytes)
|
||||
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
st, err := InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
LatestBlockHash: hashBytes,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -46,17 +47,14 @@ func TestLatestBlockHash(t *testing.T) {
|
||||
|
||||
func TestBuilderPubkey(t *testing.T) {
|
||||
t.Run("returns error before gloas", func(t *testing.T) {
|
||||
stIface, _ := util.DeterministicGenesisState(t, 1)
|
||||
native, ok := stIface.(*state_native.BeaconState)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
_, err := native.BuilderPubkey(0)
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
_, err := st.BuilderPubkey(0)
|
||||
require.ErrorContains(t, "is not supported", err)
|
||||
})
|
||||
|
||||
t.Run("returns pubkey copy", func(t *testing.T) {
|
||||
pubkey := bytes.Repeat([]byte{0xAA}, 48)
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
stIface, err := InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{
|
||||
{
|
||||
Pubkey: pubkey,
|
||||
@@ -80,12 +78,12 @@ func TestBuilderPubkey(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("out of range returns error", func(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
stIface, err := InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
st := stIface.(*state_native.BeaconState)
|
||||
st := stIface.(*BeaconState)
|
||||
_, err = st.BuilderPubkey(1)
|
||||
require.ErrorContains(t, "out of range", err)
|
||||
})
|
||||
@@ -93,7 +91,7 @@ func TestBuilderPubkey(t *testing.T) {
|
||||
|
||||
func TestBuilderHelpers(t *testing.T) {
|
||||
t.Run("is active builder", func(t *testing.T) {
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
st, err := InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{
|
||||
{
|
||||
Balance: 10,
|
||||
@@ -120,7 +118,7 @@ func TestBuilderHelpers(t *testing.T) {
|
||||
},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 2},
|
||||
}
|
||||
stInactive, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
stInactive, err := InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
active, err = stInactive.IsActiveBuilder(0)
|
||||
@@ -129,7 +127,7 @@ func TestBuilderHelpers(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("can builder cover bid", func(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
stIface, err := InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{
|
||||
{
|
||||
Balance: primitives.Gwei(params.BeaconConfig().MinDepositAmount + 50),
|
||||
@@ -147,7 +145,7 @@ func TestBuilderHelpers(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
st := stIface.(*state_native.BeaconState)
|
||||
st := stIface.(*BeaconState)
|
||||
ok, err := st.CanBuilderCoverBid(0, 20)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
@@ -159,10 +157,245 @@ func TestBuilderHelpers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuilderPendingPayments_UnsupportedVersion(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||
stIface, err := InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||
require.NoError(t, err)
|
||||
st := stIface.(*state_native.BeaconState)
|
||||
st := stIface.(*BeaconState)
|
||||
|
||||
_, err = st.BuilderPendingPayments()
|
||||
require.ErrorContains(t, "BuilderPendingPayments", err)
|
||||
}
|
||||
|
||||
func TestIsParentBlockFull(t *testing.T) {
|
||||
t.Run("returns error before gloas", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
_, err := st.IsParentBlockFull()
|
||||
require.ErrorContains(t, "is not supported", err)
|
||||
})
|
||||
|
||||
t.Run("returns false when bid is nil", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Gloas}
|
||||
got, err := st.IsParentBlockFull()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
})
|
||||
|
||||
t.Run("returns true when hashes match", func(t *testing.T) {
|
||||
hash := bytes.Repeat([]byte{0xAB}, 32)
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
latestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: hash,
|
||||
},
|
||||
latestBlockHash: hash,
|
||||
}
|
||||
|
||||
got, err := st.IsParentBlockFull()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, got)
|
||||
})
|
||||
|
||||
t.Run("returns false when hashes differ", func(t *testing.T) {
|
||||
hash := bytes.Repeat([]byte{0xAB}, 32)
|
||||
other := bytes.Repeat([]byte{0xCD}, 32)
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
latestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: hash,
|
||||
},
|
||||
latestBlockHash: other,
|
||||
}
|
||||
|
||||
got, err := st.IsParentBlockFull()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppendBuilderWithdrawals(t *testing.T) {
|
||||
t.Run("errors when prior withdrawals exceed limit", func(t *testing.T) {
|
||||
st := &BeaconState{}
|
||||
limit := params.BeaconConfig().MaxWithdrawalsPerPayload - 1
|
||||
withdrawals := make([]*enginev1.Withdrawal, limit+1)
|
||||
|
||||
nextIndex, processed, err := st.appendBuilderWithdrawals(5, &withdrawals)
|
||||
require.ErrorContains(t, "exceeds limit", err)
|
||||
require.Equal(t, uint64(5), nextIndex)
|
||||
require.Equal(t, uint64(0), processed)
|
||||
require.Equal(t, int(limit+1), len(withdrawals))
|
||||
})
|
||||
|
||||
t.Run("appends builder withdrawals and increments index", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{
|
||||
{BuilderIndex: 1, FeeRecipient: []byte{0x01}, Amount: 11},
|
||||
{BuilderIndex: 2, FeeRecipient: []byte{0x02}, Amount: 22},
|
||||
{BuilderIndex: 3, FeeRecipient: []byte{0x03}, Amount: 33},
|
||||
},
|
||||
}
|
||||
withdrawals := []*enginev1.Withdrawal{
|
||||
{Index: 7, ValidatorIndex: 9, Address: []byte{0xAA}, Amount: 99},
|
||||
}
|
||||
|
||||
nextIndex, processed, err := st.appendBuilderWithdrawals(10, &withdrawals)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(13), nextIndex)
|
||||
require.Equal(t, uint64(3), processed)
|
||||
require.Equal(t, 4, len(withdrawals))
|
||||
|
||||
require.DeepEqual(t, &enginev1.Withdrawal{
|
||||
Index: 10,
|
||||
ValidatorIndex: primitives.BuilderIndex(1).ToValidatorIndex(),
|
||||
Address: []byte{0x01},
|
||||
Amount: 11,
|
||||
}, withdrawals[1])
|
||||
require.DeepEqual(t, &enginev1.Withdrawal{
|
||||
Index: 11,
|
||||
ValidatorIndex: primitives.BuilderIndex(2).ToValidatorIndex(),
|
||||
Address: []byte{0x02},
|
||||
Amount: 22,
|
||||
}, withdrawals[2])
|
||||
require.DeepEqual(t, &enginev1.Withdrawal{
|
||||
Index: 12,
|
||||
ValidatorIndex: primitives.BuilderIndex(3).ToValidatorIndex(),
|
||||
Address: []byte{0x03},
|
||||
Amount: 33,
|
||||
}, withdrawals[3])
|
||||
})
|
||||
|
||||
t.Run("respects per-payload limit", func(t *testing.T) {
|
||||
limit := params.BeaconConfig().MaxWithdrawalsPerPayload - 1
|
||||
st := &BeaconState{
|
||||
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{
|
||||
{BuilderIndex: 4, FeeRecipient: []byte{0x04}, Amount: 44},
|
||||
{BuilderIndex: 5, FeeRecipient: []byte{0x05}, Amount: 55},
|
||||
},
|
||||
}
|
||||
withdrawals := make([]*enginev1.Withdrawal, limit-1)
|
||||
|
||||
nextIndex, processed, err := st.appendBuilderWithdrawals(20, &withdrawals)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(21), nextIndex)
|
||||
require.Equal(t, uint64(1), processed)
|
||||
require.Equal(t, int(limit), len(withdrawals))
|
||||
require.DeepEqual(t, &enginev1.Withdrawal{
|
||||
Index: 20,
|
||||
ValidatorIndex: primitives.BuilderIndex(4).ToValidatorIndex(),
|
||||
Address: []byte{0x04},
|
||||
Amount: 44,
|
||||
}, withdrawals[len(withdrawals)-1])
|
||||
})
|
||||
|
||||
t.Run("does not append when already at limit", func(t *testing.T) {
|
||||
limit := params.BeaconConfig().MaxWithdrawalsPerPayload - 1
|
||||
if limit == 0 {
|
||||
t.Skip("withdrawals limit too small")
|
||||
}
|
||||
st := &BeaconState{
|
||||
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{
|
||||
{BuilderIndex: 6, FeeRecipient: []byte{0x06}, Amount: 66},
|
||||
},
|
||||
}
|
||||
withdrawals := make([]*enginev1.Withdrawal, limit)
|
||||
|
||||
nextIndex, processed, err := st.appendBuilderWithdrawals(30, &withdrawals)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(30), nextIndex)
|
||||
require.Equal(t, uint64(0), processed)
|
||||
require.Equal(t, int(limit), len(withdrawals))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppendBuildersSweepWithdrawals(t *testing.T) {
|
||||
t.Run("errors when prior withdrawals exceed limit", func(t *testing.T) {
|
||||
st := &BeaconState{}
|
||||
limit := params.BeaconConfig().MaxWithdrawalsPerPayload - 1
|
||||
withdrawals := make([]*enginev1.Withdrawal, limit+1)
|
||||
|
||||
nextIndex, nextBuilderIndex, err := st.appendBuildersSweepWithdrawals(5, &withdrawals)
|
||||
require.ErrorContains(t, "exceeds limit", err)
|
||||
require.Equal(t, uint64(5), nextIndex)
|
||||
require.Equal(t, primitives.BuilderIndex(0), nextBuilderIndex)
|
||||
require.Equal(t, int(limit+1), len(withdrawals))
|
||||
})
|
||||
|
||||
t.Run("appends eligible builders, skips ineligible", func(t *testing.T) {
|
||||
epoch := primitives.Epoch(3)
|
||||
st := &BeaconState{
|
||||
slot: slots.UnsafeEpochStart(epoch),
|
||||
nextWithdrawalBuilderIndex: 2,
|
||||
builders: []*ethpb.Builder{
|
||||
{ExecutionAddress: []byte{0x01}, Balance: 0, WithdrawableEpoch: epoch},
|
||||
{ExecutionAddress: []byte{0x02}, Balance: 10, WithdrawableEpoch: epoch + 1},
|
||||
{ExecutionAddress: []byte{0x03}, Balance: 20, WithdrawableEpoch: epoch},
|
||||
},
|
||||
}
|
||||
withdrawals := []*enginev1.Withdrawal{}
|
||||
|
||||
nextIndex, nextBuilderIndex, err := st.appendBuildersSweepWithdrawals(100, &withdrawals)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(101), nextIndex)
|
||||
require.Equal(t, primitives.BuilderIndex(2), nextBuilderIndex)
|
||||
require.Equal(t, 1, len(withdrawals))
|
||||
require.DeepEqual(t, &enginev1.Withdrawal{
|
||||
Index: 100,
|
||||
ValidatorIndex: primitives.BuilderIndex(2).ToValidatorIndex(),
|
||||
Address: []byte{0x03},
|
||||
Amount: 20,
|
||||
}, withdrawals[0])
|
||||
})
|
||||
|
||||
t.Run("respects max builders per sweep", func(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
max := int(cfg.MaxBuildersPerWithdrawalsSweep)
|
||||
epoch := primitives.Epoch(1)
|
||||
builders := make([]*ethpb.Builder, max+2)
|
||||
for i := range builders {
|
||||
builders[i] = ðpb.Builder{
|
||||
ExecutionAddress: []byte{byte(i + 1)},
|
||||
Balance: 1,
|
||||
WithdrawableEpoch: epoch,
|
||||
}
|
||||
}
|
||||
start := len(builders) - 1
|
||||
st := &BeaconState{
|
||||
slot: slots.UnsafeEpochStart(epoch),
|
||||
nextWithdrawalBuilderIndex: primitives.BuilderIndex(start),
|
||||
builders: builders,
|
||||
}
|
||||
withdrawals := []*enginev1.Withdrawal{}
|
||||
|
||||
nextIndex, nextBuilderIndex, err := st.appendBuildersSweepWithdrawals(7, &withdrawals)
|
||||
require.NoError(t, err)
|
||||
limit := int(cfg.MaxWithdrawalsPerPayload - 1)
|
||||
expectedCount := min(max, limit)
|
||||
require.Equal(t, uint64(7)+uint64(expectedCount), nextIndex)
|
||||
require.Equal(t, expectedCount, len(withdrawals))
|
||||
expectedNext := primitives.BuilderIndex((uint64(start) + uint64(expectedCount)) % uint64(len(builders)))
|
||||
require.Equal(t, expectedNext, nextBuilderIndex)
|
||||
})
|
||||
|
||||
t.Run("stops when payload limit reached", func(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
limit := cfg.MaxWithdrawalsPerPayload - 1
|
||||
if limit < 1 {
|
||||
t.Skip("withdrawals limit too small")
|
||||
}
|
||||
epoch := primitives.Epoch(2)
|
||||
builders := []*ethpb.Builder{
|
||||
{ExecutionAddress: []byte{0x0A}, Balance: 3, WithdrawableEpoch: epoch},
|
||||
{ExecutionAddress: []byte{0x0B}, Balance: 4, WithdrawableEpoch: epoch},
|
||||
}
|
||||
st := &BeaconState{
|
||||
slot: slots.UnsafeEpochStart(epoch),
|
||||
nextWithdrawalBuilderIndex: 0,
|
||||
builders: builders,
|
||||
}
|
||||
withdrawals := make([]*enginev1.Withdrawal, limit)
|
||||
|
||||
nextIndex, nextBuilderIndex, err := st.appendBuildersSweepWithdrawals(20, &withdrawals)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(20), nextIndex)
|
||||
require.Equal(t, int(limit), len(withdrawals))
|
||||
require.Equal(t, primitives.BuilderIndex(0), nextBuilderIndex)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -133,11 +133,22 @@ func (b *BeaconState) appendPendingPartialWithdrawals(withdrawalIndex uint64, wi
|
||||
return withdrawalIndex, 0, nil
|
||||
}
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
withdrawalsLimit := min(
|
||||
len(*withdrawals)+int(cfg.MaxPendingPartialsPerWithdrawalsSweep),
|
||||
int(cfg.MaxWithdrawalsPerPayload-1),
|
||||
)
|
||||
if len(*withdrawals) > withdrawalsLimit {
|
||||
return withdrawalIndex, 0, fmt.Errorf("prior withdrawals length %d exceeds limit %d", len(*withdrawals), withdrawalsLimit)
|
||||
}
|
||||
|
||||
ws := *withdrawals
|
||||
epoch := slots.ToEpoch(b.slot)
|
||||
var processedPartialWithdrawalsCount uint64
|
||||
for _, w := range b.pendingPartialWithdrawals {
|
||||
if w.WithdrawableEpoch > epoch || len(ws) >= int(params.BeaconConfig().MaxPendingPartialsPerWithdrawalsSweep) {
|
||||
isWithdrawable := w.WithdrawableEpoch <= epoch
|
||||
hasReachedLimit := len(ws) >= withdrawalsLimit
|
||||
if !isWithdrawable || hasReachedLimit {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -149,7 +160,7 @@ func (b *BeaconState) appendPendingPartialWithdrawals(withdrawalIndex uint64, wi
|
||||
if err != nil {
|
||||
return withdrawalIndex, 0, fmt.Errorf("could not retrieve balance at index %d: %w", w.Index, err)
|
||||
}
|
||||
hasSufficientEffectiveBalance := v.EffectiveBalance() >= params.BeaconConfig().MinActivationBalance
|
||||
hasSufficientEffectiveBalance := v.EffectiveBalance() >= cfg.MinActivationBalance
|
||||
var totalWithdrawn uint64
|
||||
for _, wi := range ws {
|
||||
if wi.ValidatorIndex == w.Index {
|
||||
@@ -160,9 +171,9 @@ func (b *BeaconState) appendPendingPartialWithdrawals(withdrawalIndex uint64, wi
|
||||
if err != nil {
|
||||
return withdrawalIndex, 0, errors.Wrapf(err, "failed to subtract balance %d with total withdrawn %d", vBal, totalWithdrawn)
|
||||
}
|
||||
hasExcessBalance := balance > params.BeaconConfig().MinActivationBalance
|
||||
if v.ExitEpoch() == params.BeaconConfig().FarFutureEpoch && hasSufficientEffectiveBalance && hasExcessBalance {
|
||||
amount := min(balance-params.BeaconConfig().MinActivationBalance, w.Amount)
|
||||
hasExcessBalance := balance > cfg.MinActivationBalance
|
||||
if v.ExitEpoch() == cfg.FarFutureEpoch && hasSufficientEffectiveBalance && hasExcessBalance {
|
||||
amount := min(balance-cfg.MinActivationBalance, w.Amount)
|
||||
ws = append(ws, &enginev1.Withdrawal{
|
||||
Index: withdrawalIndex,
|
||||
ValidatorIndex: w.Index,
|
||||
@@ -183,7 +194,7 @@ func (b *BeaconState) appendValidatorsSweepWithdrawals(withdrawalIndex uint64, w
|
||||
validatorIndex := b.nextWithdrawalValidatorIndex
|
||||
validatorsLen := b.validatorsLen()
|
||||
epoch := slots.ToEpoch(b.slot)
|
||||
bound := min(uint64(validatorsLen), params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
|
||||
bound := min(validatorsLen, int(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep))
|
||||
for range bound {
|
||||
val, err := b.validatorAtIndexReadOnly(validatorIndex)
|
||||
if err != nil {
|
||||
@@ -222,7 +233,7 @@ func (b *BeaconState) appendValidatorsSweepWithdrawals(withdrawalIndex uint64, w
|
||||
})
|
||||
withdrawalIndex++
|
||||
}
|
||||
if uint64(len(ws)) == params.BeaconConfig().MaxWithdrawalsPerPayload {
|
||||
if len(ws) == int(params.BeaconConfig().MaxWithdrawalsPerPayload) {
|
||||
break
|
||||
}
|
||||
validatorIndex += 1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package state_native
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native/types"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
)
|
||||
@@ -161,3 +163,154 @@ func (b *BeaconState) UpdateExecutionPayloadAvailabilityAtIndex(idx uint64, val
|
||||
b.markFieldAsDirty(types.ExecutionPayloadAvailability)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPayloadExpectedWithdrawals stores the expected withdrawals for the next payload.
|
||||
func (b *BeaconState) SetPayloadExpectedWithdrawals(withdrawals []*enginev1.Withdrawal) error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("SetPayloadExpectedWithdrawals", b.version)
|
||||
}
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
b.payloadExpectedWithdrawals = withdrawals
|
||||
b.markFieldAsDirty(types.PayloadExpectedWithdrawals)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DequeueBuilderPendingWithdrawals removes processed builder withdrawals from the front of the queue.
|
||||
func (b *BeaconState) DequeueBuilderPendingWithdrawals(n uint64) error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("DequeueBuilderPendingWithdrawals", b.version)
|
||||
}
|
||||
|
||||
if n > uint64(len(b.builderPendingWithdrawals)) {
|
||||
return errors.New("cannot dequeue more builder withdrawals than are in the queue")
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if b.sharedFieldReferences[types.BuilderPendingWithdrawals].Refs() > 1 {
|
||||
withdrawals := make([]*ethpb.BuilderPendingWithdrawal, len(b.builderPendingWithdrawals))
|
||||
copy(withdrawals, b.builderPendingWithdrawals)
|
||||
b.builderPendingWithdrawals = withdrawals
|
||||
b.sharedFieldReferences[types.BuilderPendingWithdrawals].MinusRef()
|
||||
b.sharedFieldReferences[types.BuilderPendingWithdrawals] = stateutil.NewRef(1)
|
||||
}
|
||||
|
||||
b.builderPendingWithdrawals = b.builderPendingWithdrawals[n:]
|
||||
b.markFieldAsDirty(types.BuilderPendingWithdrawals)
|
||||
b.rebuildTrie[types.BuilderPendingWithdrawals] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetNextWithdrawalBuilderIndex sets the next builder index for the withdrawals sweep.
|
||||
func (b *BeaconState) SetNextWithdrawalBuilderIndex(index primitives.BuilderIndex) error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("SetNextWithdrawalBuilderIndex", b.version)
|
||||
}
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
b.nextWithdrawalBuilderIndex = index
|
||||
b.markFieldAsDirty(types.NextWithdrawalBuilderIndex)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecreaseWithdrawalBalances applies withdrawal balance decreases for validators and builders.
|
||||
// This method holds the state lock for the full batch to avoid lock churn.
|
||||
func (b *BeaconState) DecreaseWithdrawalBalances(withdrawals []*enginev1.Withdrawal) error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("DecreaseWithdrawalBalances", b.version)
|
||||
}
|
||||
if len(withdrawals) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
var (
|
||||
balanceIndices []uint64
|
||||
builderIndices []uint64
|
||||
)
|
||||
|
||||
for _, withdrawal := range withdrawals {
|
||||
if withdrawal == nil {
|
||||
return errors.New("withdrawal is nil")
|
||||
}
|
||||
if withdrawal.Amount == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if withdrawal.ValidatorIndex.IsBuilderIndex() {
|
||||
builderIndex := withdrawal.ValidatorIndex.ToBuilderIndex()
|
||||
if err := b.decreaseBuilderBalanceLockFree(builderIndex, withdrawal.Amount); err != nil {
|
||||
return err
|
||||
}
|
||||
builderIndices = append(builderIndices, uint64(builderIndex))
|
||||
continue
|
||||
}
|
||||
|
||||
balAtIdx, err := b.balanceAtIndex(withdrawal.ValidatorIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBal := decreaseBalanceWithVal(balAtIdx, withdrawal.Amount)
|
||||
if err := b.balancesMultiValue.UpdateAt(b, uint64(withdrawal.ValidatorIndex), newBal); err != nil {
|
||||
return fmt.Errorf("could not update balances: %w", err)
|
||||
}
|
||||
balanceIndices = append(balanceIndices, uint64(withdrawal.ValidatorIndex))
|
||||
}
|
||||
|
||||
if len(balanceIndices) > 0 {
|
||||
b.markFieldAsDirty(types.Balances)
|
||||
b.addDirtyIndices(types.Balances, balanceIndices)
|
||||
}
|
||||
if len(builderIndices) > 0 {
|
||||
b.markFieldAsDirty(types.Builders)
|
||||
b.addDirtyIndices(types.Builders, builderIndices)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BeaconState) decreaseBuilderBalanceLockFree(builderIndex primitives.BuilderIndex, amount uint64) error {
|
||||
idx := uint64(builderIndex)
|
||||
if idx >= uint64(len(b.builders)) {
|
||||
return fmt.Errorf("builder index %d out of range (len=%d)", builderIndex, len(b.builders))
|
||||
}
|
||||
|
||||
if b.sharedFieldReferences[types.Builders].Refs() > 1 {
|
||||
builders := make([]*ethpb.Builder, len(b.builders))
|
||||
copy(builders, b.builders)
|
||||
b.builders = builders
|
||||
b.sharedFieldReferences[types.Builders].MinusRef()
|
||||
b.sharedFieldReferences[types.Builders] = stateutil.NewRef(1)
|
||||
}
|
||||
|
||||
builder := b.builders[idx]
|
||||
bal := uint64(builder.Balance)
|
||||
if amount >= bal {
|
||||
builder.Balance = 0
|
||||
} else {
|
||||
builder.Balance = primitives.Gwei(bal - amount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decreaseBalanceWithVal(currBalance, delta uint64) uint64 {
|
||||
if delta > currBalance {
|
||||
return 0
|
||||
}
|
||||
return currBalance - delta
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stateutil"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
@@ -229,6 +230,235 @@ func TestRotateBuilderPendingPayments_UnsupportedVersion(t *testing.T) {
|
||||
require.ErrorContains(t, "RotateBuilderPendingPayments", err)
|
||||
}
|
||||
|
||||
func TestSetPayloadExpectedWithdrawals(t *testing.T) {
|
||||
t.Run("previous fork returns expected error", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
err := st.SetPayloadExpectedWithdrawals([]*enginev1.Withdrawal{})
|
||||
require.ErrorContains(t, "SetPayloadExpectedWithdrawals", err)
|
||||
})
|
||||
|
||||
t.Run("allows nil input and marks dirty", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
}
|
||||
|
||||
require.NoError(t, st.SetPayloadExpectedWithdrawals(nil))
|
||||
require.Equal(t, true, st.payloadExpectedWithdrawals == nil)
|
||||
require.Equal(t, true, st.dirtyFields[types.PayloadExpectedWithdrawals])
|
||||
})
|
||||
|
||||
t.Run("sets and marks dirty", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
payloadExpectedWithdrawals: []*enginev1.Withdrawal{{Index: 1}, {Index: 2}},
|
||||
}
|
||||
|
||||
withdrawals := []*enginev1.Withdrawal{{Index: 3}}
|
||||
require.NoError(t, st.SetPayloadExpectedWithdrawals(withdrawals))
|
||||
|
||||
require.DeepEqual(t, withdrawals, st.payloadExpectedWithdrawals)
|
||||
require.Equal(t, true, st.dirtyFields[types.PayloadExpectedWithdrawals])
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecreaseWithdrawalBalances(t *testing.T) {
|
||||
t.Run("previous fork returns expected error", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
err := st.DecreaseWithdrawalBalances([]*enginev1.Withdrawal{{}})
|
||||
require.ErrorContains(t, "DecreaseWithdrawalBalances", err)
|
||||
})
|
||||
|
||||
t.Run("rejects nil withdrawal", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Gloas}
|
||||
err := st.DecreaseWithdrawalBalances([]*enginev1.Withdrawal{nil})
|
||||
require.ErrorContains(t, "withdrawal is nil", err)
|
||||
})
|
||||
|
||||
t.Run("no-op on empty input", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
dirtyIndices: make(map[types.FieldIndex][]uint64),
|
||||
rebuildTrie: make(map[types.FieldIndex]bool),
|
||||
}
|
||||
|
||||
require.NoError(t, st.DecreaseWithdrawalBalances(nil))
|
||||
require.Equal(t, 0, len(st.dirtyFields))
|
||||
require.Equal(t, 0, len(st.dirtyIndices))
|
||||
})
|
||||
|
||||
t.Run("updates validator and builder balances and tracks dirty indices", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
dirtyIndices: make(map[types.FieldIndex][]uint64),
|
||||
rebuildTrie: make(map[types.FieldIndex]bool),
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.Builders: stateutil.NewRef(1),
|
||||
},
|
||||
balancesMultiValue: NewMultiValueBalances([]uint64{100, 200, 300}),
|
||||
builders: []*ethpb.Builder{
|
||||
{Balance: 1000},
|
||||
{Balance: 50},
|
||||
},
|
||||
}
|
||||
|
||||
withdrawals := []*enginev1.Withdrawal{
|
||||
{ValidatorIndex: primitives.ValidatorIndex(1), Amount: 20},
|
||||
{ValidatorIndex: primitives.BuilderIndex(1).ToValidatorIndex(), Amount: 30},
|
||||
{ValidatorIndex: primitives.ValidatorIndex(2), Amount: 400},
|
||||
{ValidatorIndex: primitives.BuilderIndex(0).ToValidatorIndex(), Amount: 2000},
|
||||
{ValidatorIndex: primitives.ValidatorIndex(0), Amount: 0},
|
||||
}
|
||||
|
||||
require.NoError(t, st.DecreaseWithdrawalBalances(withdrawals))
|
||||
|
||||
require.DeepEqual(t, []uint64{100, 180, 0}, st.Balances())
|
||||
require.Equal(t, primitives.Gwei(0), st.builders[0].Balance)
|
||||
require.Equal(t, primitives.Gwei(20), st.builders[1].Balance)
|
||||
|
||||
require.Equal(t, true, st.dirtyFields[types.Balances])
|
||||
require.Equal(t, true, st.dirtyFields[types.Builders])
|
||||
require.DeepEqual(t, []uint64{1, 2}, st.dirtyIndices[types.Balances])
|
||||
require.DeepEqual(t, []uint64{1, 0}, st.dirtyIndices[types.Builders])
|
||||
})
|
||||
|
||||
t.Run("returns error on builder index out of range", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
dirtyIndices: make(map[types.FieldIndex][]uint64),
|
||||
rebuildTrie: make(map[types.FieldIndex]bool),
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.Builders: stateutil.NewRef(1),
|
||||
},
|
||||
builders: []*ethpb.Builder{{Balance: 5}},
|
||||
}
|
||||
|
||||
err := st.DecreaseWithdrawalBalances([]*enginev1.Withdrawal{
|
||||
{ValidatorIndex: primitives.BuilderIndex(2).ToValidatorIndex(), Amount: 1},
|
||||
})
|
||||
require.ErrorContains(t, "out of range", err)
|
||||
require.Equal(t, false, st.dirtyFields[types.Builders])
|
||||
require.Equal(t, 0, len(st.dirtyIndices[types.Builders]))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDequeueBuilderPendingWithdrawals(t *testing.T) {
|
||||
t.Run("previous fork returns expected error", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
err := st.DequeueBuilderPendingWithdrawals(1)
|
||||
require.ErrorContains(t, "DequeueBuilderPendingWithdrawals", err)
|
||||
})
|
||||
|
||||
t.Run("returns error when dequeueing more than length", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.BuilderPendingWithdrawals: stateutil.NewRef(1),
|
||||
},
|
||||
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{{Amount: 1}},
|
||||
}
|
||||
|
||||
err := st.DequeueBuilderPendingWithdrawals(2)
|
||||
require.ErrorContains(t, "cannot dequeue more builder withdrawals", err)
|
||||
require.Equal(t, 1, len(st.builderPendingWithdrawals))
|
||||
require.Equal(t, false, st.dirtyFields[types.BuilderPendingWithdrawals])
|
||||
})
|
||||
|
||||
t.Run("no-op on zero", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.BuilderPendingWithdrawals: stateutil.NewRef(1),
|
||||
},
|
||||
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{{Amount: 1}},
|
||||
}
|
||||
|
||||
require.NoError(t, st.DequeueBuilderPendingWithdrawals(0))
|
||||
require.Equal(t, 1, len(st.builderPendingWithdrawals))
|
||||
require.Equal(t, false, st.dirtyFields[types.BuilderPendingWithdrawals])
|
||||
require.Equal(t, false, st.rebuildTrie[types.BuilderPendingWithdrawals])
|
||||
})
|
||||
|
||||
t.Run("dequeues and marks dirty", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.BuilderPendingWithdrawals: stateutil.NewRef(1),
|
||||
},
|
||||
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{
|
||||
{Amount: 1},
|
||||
{Amount: 2},
|
||||
{Amount: 3},
|
||||
},
|
||||
rebuildTrie: make(map[types.FieldIndex]bool),
|
||||
}
|
||||
|
||||
require.NoError(t, st.DequeueBuilderPendingWithdrawals(2))
|
||||
require.Equal(t, 1, len(st.builderPendingWithdrawals))
|
||||
require.Equal(t, primitives.Gwei(3), st.builderPendingWithdrawals[0].Amount)
|
||||
require.Equal(t, true, st.dirtyFields[types.BuilderPendingWithdrawals])
|
||||
require.Equal(t, true, st.rebuildTrie[types.BuilderPendingWithdrawals])
|
||||
})
|
||||
|
||||
t.Run("copy-on-write preserves shared state", func(t *testing.T) {
|
||||
sharedRef := stateutil.NewRef(2)
|
||||
sharedWithdrawals := []*ethpb.BuilderPendingWithdrawal{
|
||||
{Amount: 1},
|
||||
{Amount: 2},
|
||||
{Amount: 3},
|
||||
}
|
||||
|
||||
st1 := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.BuilderPendingWithdrawals: sharedRef,
|
||||
},
|
||||
builderPendingWithdrawals: sharedWithdrawals,
|
||||
rebuildTrie: make(map[types.FieldIndex]bool),
|
||||
}
|
||||
st2 := &BeaconState{
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.BuilderPendingWithdrawals: sharedRef,
|
||||
},
|
||||
builderPendingWithdrawals: sharedWithdrawals,
|
||||
}
|
||||
|
||||
require.NoError(t, st1.DequeueBuilderPendingWithdrawals(2))
|
||||
require.Equal(t, primitives.Gwei(3), st1.builderPendingWithdrawals[0].Amount)
|
||||
require.Equal(t, 3, len(st2.builderPendingWithdrawals))
|
||||
require.Equal(t, primitives.Gwei(1), st2.builderPendingWithdrawals[0].Amount)
|
||||
require.Equal(t, uint(1), st1.sharedFieldReferences[types.BuilderPendingWithdrawals].Refs())
|
||||
require.Equal(t, uint(1), st2.sharedFieldReferences[types.BuilderPendingWithdrawals].Refs())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetNextWithdrawalBuilderIndex(t *testing.T) {
|
||||
t.Run("previous fork returns expected error", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
err := st.SetNextWithdrawalBuilderIndex(1)
|
||||
require.ErrorContains(t, "SetNextWithdrawalBuilderIndex", err)
|
||||
})
|
||||
|
||||
t.Run("sets and marks dirty", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
}
|
||||
|
||||
require.NoError(t, st.SetNextWithdrawalBuilderIndex(7))
|
||||
require.Equal(t, primitives.BuilderIndex(7), st.nextWithdrawalBuilderIndex)
|
||||
require.Equal(t, true, st.dirtyFields[types.NextWithdrawalBuilderIndex])
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppendBuilderPendingWithdrawal_CopyOnWrite(t *testing.T) {
|
||||
wd := ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
|
||||
2
changelog/tt_process-withdrawals-gloas.md
Normal file
2
changelog/tt_process-withdrawals-gloas.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Added
|
||||
- add modified process withdrawals for gloas
|
||||
@@ -47,6 +47,7 @@ type BeaconChainConfig struct {
|
||||
HysteresisQuotient uint64 `yaml:"HYSTERESIS_QUOTIENT" spec:"true"` // HysteresisQuotient defines the hysteresis quotient for effective balance calculations.
|
||||
HysteresisDownwardMultiplier uint64 `yaml:"HYSTERESIS_DOWNWARD_MULTIPLIER" spec:"true"` // HysteresisDownwardMultiplier defines the hysteresis downward multiplier for effective balance calculations.
|
||||
HysteresisUpwardMultiplier uint64 `yaml:"HYSTERESIS_UPWARD_MULTIPLIER" spec:"true"` // HysteresisUpwardMultiplier defines the hysteresis upward multiplier for effective balance calculations.
|
||||
BuilderIndexFlag uint64 `yaml:"BUILDER_INDEX_FLAG" spec:"true"` // BuilderIndexFlag marks a ValidatorIndex as a BuilderIndex when the bit is set.
|
||||
|
||||
// Gwei value constants.
|
||||
MinDepositAmount uint64 `yaml:"MIN_DEPOSIT_AMOUNT" spec:"true"` // MinDepositAmount is the minimum amount of Gwei a validator can send to the deposit contract at once (lower amounts will be reverted).
|
||||
@@ -130,6 +131,7 @@ type BeaconChainConfig struct {
|
||||
MaxWithdrawalsPerPayload uint64 `yaml:"MAX_WITHDRAWALS_PER_PAYLOAD" spec:"true"` // MaxWithdrawalsPerPayload defines the maximum number of withdrawals in a block.
|
||||
MaxBlsToExecutionChanges uint64 `yaml:"MAX_BLS_TO_EXECUTION_CHANGES" spec:"true"` // MaxBlsToExecutionChanges defines the maximum number of BLS-to-execution-change objects in a block.
|
||||
MaxValidatorsPerWithdrawalsSweep uint64 `yaml:"MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP" spec:"true"` // MaxValidatorsPerWithdrawalsSweep bounds the size of the sweep searching for withdrawals per slot.
|
||||
MaxBuildersPerWithdrawalsSweep uint64 `yaml:"MAX_BUILDERS_PER_WITHDRAWALS_SWEEP" spec:"true"` // MaxBuildersPerWithdrawalsSweep bounds the size of the builder withdrawals sweep per slot.
|
||||
|
||||
// BLS domain values.
|
||||
DomainBeaconProposer [4]byte `yaml:"DOMAIN_BEACON_PROPOSER" spec:"true"` // DomainBeaconProposer defines the BLS signature domain for beacon proposal verification.
|
||||
|
||||
@@ -194,6 +194,8 @@ func ConfigToYaml(cfg *BeaconChainConfig) []byte {
|
||||
fmt.Sprintf("SHARD_COMMITTEE_PERIOD: %d", cfg.ShardCommitteePeriod),
|
||||
fmt.Sprintf("MIN_VALIDATOR_WITHDRAWABILITY_DELAY: %d", cfg.MinValidatorWithdrawabilityDelay),
|
||||
fmt.Sprintf("MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: %d", cfg.MaxValidatorsPerWithdrawalsSweep),
|
||||
fmt.Sprintf("MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: %d", cfg.MaxBuildersPerWithdrawalsSweep),
|
||||
fmt.Sprintf("BUILDER_INDEX_FLAG: %d", cfg.BuilderIndexFlag),
|
||||
fmt.Sprintf("MAX_SEED_LOOKAHEAD: %d", cfg.MaxSeedLookahead),
|
||||
fmt.Sprintf("EJECTION_BALANCE: %d", cfg.EjectionBalance),
|
||||
fmt.Sprintf("MIN_PER_EPOCH_CHURN_LIMIT: %d", cfg.MinPerEpochChurnLimit),
|
||||
|
||||
@@ -85,6 +85,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{
|
||||
HysteresisQuotient: 4,
|
||||
HysteresisDownwardMultiplier: 1,
|
||||
HysteresisUpwardMultiplier: 5,
|
||||
BuilderIndexFlag: 1099511627776,
|
||||
|
||||
// Gwei value constants.
|
||||
MinDepositAmount: 1 * 1e9,
|
||||
@@ -175,6 +176,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{
|
||||
MaxWithdrawalsPerPayload: 16,
|
||||
MaxBlsToExecutionChanges: 16,
|
||||
MaxValidatorsPerWithdrawalsSweep: 16384,
|
||||
MaxBuildersPerWithdrawalsSweep: 16384,
|
||||
|
||||
// BLS domain values.
|
||||
DomainBeaconProposer: bytesutil.Uint32ToBytes4(0x00000000),
|
||||
|
||||
@@ -49,6 +49,7 @@ func compareConfigs(t *testing.T, expected, actual *params.BeaconChainConfig) {
|
||||
require.DeepEqual(t, expected.HysteresisQuotient, actual.HysteresisQuotient)
|
||||
require.DeepEqual(t, expected.HysteresisDownwardMultiplier, actual.HysteresisDownwardMultiplier)
|
||||
require.DeepEqual(t, expected.HysteresisUpwardMultiplier, actual.HysteresisUpwardMultiplier)
|
||||
require.DeepEqual(t, expected.BuilderIndexFlag, actual.BuilderIndexFlag)
|
||||
require.DeepEqual(t, expected.MinDepositAmount, actual.MinDepositAmount)
|
||||
require.DeepEqual(t, expected.MaxEffectiveBalance, actual.MaxEffectiveBalance)
|
||||
require.DeepEqual(t, expected.EjectionBalance, actual.EjectionBalance)
|
||||
@@ -94,6 +95,7 @@ func compareConfigs(t *testing.T, expected, actual *params.BeaconChainConfig) {
|
||||
require.DeepEqual(t, expected.MaxDeposits, actual.MaxDeposits)
|
||||
require.DeepEqual(t, expected.MaxVoluntaryExits, actual.MaxVoluntaryExits)
|
||||
require.DeepEqual(t, expected.MaxWithdrawalsPerPayload, actual.MaxWithdrawalsPerPayload)
|
||||
require.DeepEqual(t, expected.MaxBuildersPerWithdrawalsSweep, actual.MaxBuildersPerWithdrawalsSweep)
|
||||
require.DeepEqual(t, expected.DomainBeaconProposer, actual.DomainBeaconProposer)
|
||||
require.DeepEqual(t, expected.DomainRandao, actual.DomainRandao)
|
||||
require.DeepEqual(t, expected.DomainBeaconAttester, actual.DomainBeaconAttester)
|
||||
|
||||
@@ -13,6 +13,16 @@ var _ fssz.Unmarshaler = (*BuilderIndex)(nil)
|
||||
// BuilderIndex is an index into the builder registry.
|
||||
type BuilderIndex uint64
|
||||
|
||||
// ToValidatorIndex sets the builder flag on a builder index.
|
||||
//
|
||||
// Spec v1.6.1 (pseudocode):
|
||||
// def convert_builder_index_to_validator_index(builder_index: BuilderIndex) -> ValidatorIndex:
|
||||
//
|
||||
// return ValidatorIndex(builder_index | BUILDER_INDEX_FLAG)
|
||||
func (b BuilderIndex) ToValidatorIndex() ValidatorIndex {
|
||||
return ValidatorIndex(uint64(b) | BuilderIndexFlag)
|
||||
}
|
||||
|
||||
// HashTreeRoot returns the SSZ hash tree root of the index.
|
||||
func (b BuilderIndex) HashTreeRoot() ([32]byte, error) {
|
||||
return fssz.HashWithDefaultHasher(b)
|
||||
|
||||
@@ -10,9 +10,34 @@ var _ fssz.HashRoot = (ValidatorIndex)(0)
|
||||
var _ fssz.Marshaler = (*ValidatorIndex)(nil)
|
||||
var _ fssz.Unmarshaler = (*ValidatorIndex)(nil)
|
||||
|
||||
// BuilderIndexFlag marks a ValidatorIndex as a BuilderIndex when the bit is set.
|
||||
//
|
||||
// Spec v1.6.1: BUILDER_INDEX_FLAG.
|
||||
const BuilderIndexFlag uint64 = 1 << 40
|
||||
|
||||
// ValidatorIndex in eth2.
|
||||
type ValidatorIndex uint64
|
||||
|
||||
// IsBuilderIndex returns true when the BuilderIndex flag is set on the validator index.
|
||||
//
|
||||
// Spec v1.6.1 (pseudocode):
|
||||
// def is_builder_index(validator_index: ValidatorIndex) -> bool:
|
||||
//
|
||||
// return (validator_index & BUILDER_INDEX_FLAG) != 0
|
||||
func (v ValidatorIndex) IsBuilderIndex() bool {
|
||||
return uint64(v)&BuilderIndexFlag != 0
|
||||
}
|
||||
|
||||
// ToBuilderIndex strips the builder flag from a validator index.
|
||||
//
|
||||
// Spec v1.6.1 (pseudocode):
|
||||
// def convert_validator_index_to_builder_index(validator_index: ValidatorIndex) -> BuilderIndex:
|
||||
//
|
||||
// return BuilderIndex(validator_index & ~BUILDER_INDEX_FLAG)
|
||||
func (v ValidatorIndex) ToBuilderIndex() BuilderIndex {
|
||||
return BuilderIndex(uint64(v) & ^BuilderIndexFlag)
|
||||
}
|
||||
|
||||
// Div divides validator index by x.
|
||||
// This method panics if dividing by zero!
|
||||
func (v ValidatorIndex) Div(x uint64) ValidatorIndex {
|
||||
|
||||
@@ -33,3 +33,32 @@ func TestValidatorIndex_Casting(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidatorIndex_BuilderIndexFlagConversions(t *testing.T) {
|
||||
base := uint64(42)
|
||||
|
||||
unflagged := ValidatorIndex(base)
|
||||
if unflagged.IsBuilderIndex() {
|
||||
t.Fatalf("expected unflagged validator index to not be a builder index")
|
||||
}
|
||||
if got, want := unflagged.ToBuilderIndex(), BuilderIndex(base); got != want {
|
||||
t.Fatalf("unexpected builder index: got %d want %d", got, want)
|
||||
}
|
||||
|
||||
flagged := ValidatorIndex(base | BuilderIndexFlag)
|
||||
if !flagged.IsBuilderIndex() {
|
||||
t.Fatalf("expected flagged validator index to be a builder index")
|
||||
}
|
||||
if got, want := flagged.ToBuilderIndex(), BuilderIndex(base); got != want {
|
||||
t.Fatalf("unexpected builder index: got %d want %d", got, want)
|
||||
}
|
||||
|
||||
builder := BuilderIndex(base)
|
||||
roundTrip := builder.ToValidatorIndex()
|
||||
if !roundTrip.IsBuilderIndex() {
|
||||
t.Fatalf("expected round-tripped validator index to be a builder index")
|
||||
}
|
||||
if got, want := roundTrip.ToBuilderIndex(), builder; got != want {
|
||||
t.Fatalf("unexpected round-trip builder index: got %d want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ go_test(
|
||||
"gloas__operations__execution_payload_header_test.go",
|
||||
"gloas__operations__payload_attestation_test.go",
|
||||
"gloas__operations__proposer_slashing_test.go",
|
||||
"gloas__operations__withdrawals_test.go",
|
||||
"gloas__sanity__slots_test.go",
|
||||
"gloas__ssz_static__ssz_static_test.go",
|
||||
"phase0__epoch_processing__effective_balance_updates_test.go",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package mainnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||
)
|
||||
|
||||
func TestMainnet_Gloas_Operations_Withdrawals(t *testing.T) {
|
||||
operations.RunWithdrawalsTest(t, "mainnet")
|
||||
}
|
||||
@@ -210,6 +210,7 @@ go_test(
|
||||
"gloas__operations__execution_payload_bid_test.go",
|
||||
"gloas__operations__payload_attestation_test.go",
|
||||
"gloas__operations__proposer_slashing_test.go",
|
||||
"gloas__operations__withdrawals_test.go",
|
||||
"gloas__sanity__slots_test.go",
|
||||
"gloas__ssz_static__ssz_static_test.go",
|
||||
"phase0__epoch_processing__effective_balance_updates_test.go",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package minimal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||
)
|
||||
|
||||
func TestMinimal_Gloas_Operations_Withdrawals(t *testing.T) {
|
||||
operations.RunWithdrawalsTest(t, "minimal")
|
||||
}
|
||||
@@ -8,16 +8,20 @@ go_library(
|
||||
"helpers.go",
|
||||
"payload_attestation.go",
|
||||
"proposer_slashing.go",
|
||||
"withdrawals.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/spectest/shared/common/operations:go_default_library",
|
||||
"//testing/spectest/utils:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
48
testing/spectest/shared/gloas/operations/withdrawals.go
Normal file
48
testing/spectest/shared/gloas/operations/withdrawals.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package operations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
common "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/common/operations"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/utils"
|
||||
)
|
||||
|
||||
func emptyBlockGloas() (interfaces.SignedBeaconBlock, error) {
|
||||
b := ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Body: ðpb.BeaconBlockBodyGloas{},
|
||||
},
|
||||
}
|
||||
return blocks.NewSignedBeaconBlock(b)
|
||||
}
|
||||
|
||||
func RunWithdrawalsTest(t *testing.T, config string) {
|
||||
require.NoError(t, utils.SetConfig(t, config))
|
||||
testFolders, testsFolderPath := utils.TestFolders(t, config, version.String(version.Gloas), "operations/withdrawals/pyspec_tests")
|
||||
if len(testFolders) == 0 {
|
||||
t.Fatalf("No test folders found for %s/%s/%s", config, version.String(version.Gloas), "operations/withdrawals/pyspec_tests")
|
||||
}
|
||||
for _, folder := range testFolders {
|
||||
t.Run(folder.Name(), func(t *testing.T) {
|
||||
folderPath := path.Join(testsFolderPath, folder.Name())
|
||||
blk, err := emptyBlockGloas()
|
||||
require.NoError(t, err)
|
||||
|
||||
common.RunBlockOperationTest(t, folderPath, blk, sszToState, func(_ context.Context, s state.BeaconState, _ interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, error) {
|
||||
if err := gloas.ProcessWithdrawals(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user