mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-02 17:15:21 -05:00
Compare commits
1 Commits
synced-new
...
gloas/proc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f75c3082d5 |
@@ -87,32 +87,36 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
func logBlockSyncStatus(block interfaces.ReadOnlyBeaconBlock, blockRoot [32]byte, justified, finalized *ethpb.Checkpoint, receivedTime time.Time, genesis time.Time, daWaitedTime time.Duration) error {
|
||||
startTime, err := slots.StartTime(genesis, block.Slot())
|
||||
if err != nil {
|
||||
return fmt.Errorf("start time: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log := log.WithFields(logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"block": fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8]),
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"slotInEpoch": block.Slot() % params.BeaconConfig().SlotsPerEpoch,
|
||||
"sinceSlotStartTime": prysmTime.Now().Sub(startTime),
|
||||
})
|
||||
|
||||
if log.Logger.GetLevel() >= logrus.DebugLevel {
|
||||
level := log.Logger.GetLevel()
|
||||
if level >= logrus.DebugLevel {
|
||||
parentRoot := block.ParentRoot()
|
||||
log = log.WithFields(logrus.Fields{
|
||||
lf := logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"slotInEpoch": block.Slot() % params.BeaconConfig().SlotsPerEpoch,
|
||||
"block": fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8]),
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"justifiedEpoch": justified.Epoch,
|
||||
"justifiedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(justified.Root)[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8]),
|
||||
"parentRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(parentRoot[:])[:8]),
|
||||
"version": version.String(block.Version()),
|
||||
"sinceSlotStartTime": prysmTime.Now().Sub(startTime),
|
||||
"chainServiceProcessedTime": prysmTime.Now().Sub(receivedTime) - daWaitedTime,
|
||||
"dataAvailabilityWaitedTime": daWaitedTime,
|
||||
})
|
||||
}
|
||||
log.WithFields(lf).Debug("Synced new block")
|
||||
} else {
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"block": fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8]),
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
}).Info("Synced new block")
|
||||
}
|
||||
|
||||
log.Info("Synced new block")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### Changed
|
||||
|
||||
- Always use `INFO` for the "Synced new block" log, but add additional data if the verbosity is set to `debug` or higher.
|
||||
- Add `slotInEpoch` and `sinceSlotStartTime` fields to the "Synced new block" INFO log.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Add handy documentation for SSZ Query package (`encoding/ssz/query`).
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
# SSZ Query Package
|
||||
|
||||
The `encoding/ssz/query` package provides a system for analyzing and querying SSZ ([Simple Serialize](https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md)) data structures, as well as generating Merkle proofs from them. It enables runtime analysis of SSZ-serialized Go objects with reflection, path-based queries through nested structures, generalized index calculation, and Merkle proof generation.
|
||||
|
||||
This package is designed to be generic. It operates on arbitrary SSZ-serialized Go values at runtime, so the same query/proof machinery applies equally to any SSZ type, including the BeaconState/BeaconBlock.
|
||||
|
||||
## Usage Example
|
||||
|
||||
```go
|
||||
// 1. Analyze an SSZ object
|
||||
block := ðpb.BeaconBlock{...}
|
||||
info, err := query.AnalyzeObject(block)
|
||||
|
||||
// 2. Parse a path
|
||||
path, err := query.ParsePath(".body.attestations[0].data.slot")
|
||||
|
||||
// 3. Get the generalized index
|
||||
gindex, err := query.GetGeneralizedIndexFromPath(info, path)
|
||||
|
||||
// 4. Generate a Merkle proof
|
||||
proof, err := info.Prove(gindex)
|
||||
|
||||
// 5. Get offset and length to slice the SSZ-encoded bytes
|
||||
sszBytes, _ := block.MarshalSSZ()
|
||||
_, offset, length, err := query.CalculateOffsetAndLength(info, path)
|
||||
// slotBytes contains the SSZ-encoded value at the queried path
|
||||
slotBytes := sszBytes[offset : offset+length]
|
||||
```
|
||||
|
||||
## Exported API
|
||||
|
||||
The main exported API consists of:
|
||||
|
||||
```go
|
||||
// AnalyzeObject analyzes an SSZ object and returns its structural information
|
||||
func AnalyzeObject(obj SSZObject) (*SszInfo, error)
|
||||
|
||||
// ParsePath parses a path string like ".field1.field2[0].field3"
|
||||
func ParsePath(rawPath string) (Path, error)
|
||||
|
||||
// CalculateOffsetAndLength computes byte offset and length for a path within an SSZ object
|
||||
func CalculateOffsetAndLength(sszInfo *SszInfo, path Path) (*SszInfo, uint64, uint64, error)
|
||||
|
||||
// GetGeneralizedIndexFromPath calculates the generalized index for a given path
|
||||
func GetGeneralizedIndexFromPath(info *SszInfo, path Path) (uint64, error)
|
||||
|
||||
// Prove generates a Merkle proof for a target generalized index
|
||||
func (s *SszInfo) Prove(gindex uint64) (*fastssz.Proof, error)
|
||||
```
|
||||
|
||||
## Type System
|
||||
|
||||
### SSZ Types
|
||||
|
||||
The package now supports [all standard SSZ types](https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md#typing) except `ProgressiveList`, `ProgressiveContainer`, `ProgressiveBitlist`, `Union`, and `CompatibleUnion`.
|
||||
|
||||
### Core Data Structures
|
||||
|
||||
#### `SszInfo`
|
||||
|
||||
The `SszInfo` structure contains complete structural metadata for an SSZ type:
|
||||
|
||||
```go
|
||||
type SszInfo struct {
|
||||
sszType SszType // SSZ Type classification
|
||||
typ reflect.Type // Go reflect.Type
|
||||
source SSZObject // Original SSZObject reference. Mostly used for reusing SSZ methods like `HashTreeRoot`.
|
||||
isVariable bool // True if contains variable-size fields
|
||||
|
||||
// Composite types have corresponding metadata. Other fields would be nil except for the current type.
|
||||
containerInfo *containerInfo
|
||||
listInfo *listInfo
|
||||
vectorInfo *vectorInfo
|
||||
bitlistInfo *bitlistInfo
|
||||
bitvectorInfo *bitvectorInfo
|
||||
}
|
||||
```
|
||||
|
||||
#### `Path`
|
||||
|
||||
The `Path` structure represents navigation paths through SSZ structures. It supports accessing a field by field name, accessing an element by index (list/vector type), and finding the length of homogenous collection types. The `ParsePath` function parses a raw string into a `Path` instance, which is commonly used in other APIs like `CalculateOffsetAndLength` and `GetGeneralizedIndexFromPath`.
|
||||
|
||||
```go
|
||||
type Path struct {
|
||||
Length bool // Flag for length queries (e.g., len(.field))
|
||||
Elements []PathElement // Sequence of field accesses and indices
|
||||
}
|
||||
|
||||
type PathElement struct {
|
||||
Name string // Field name
|
||||
Index *uint64 // list/vector index (nil if not an index access)
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Type Analysis (`analyzer.go`)
|
||||
|
||||
The `AnalyzeObject` function performs recursive type introspection using Go reflection:
|
||||
|
||||
1. **Type Inspection** - Examines Go `reflect.Value` to determine SSZ type
|
||||
- Basic types (`uint8`, `uint16`, `uint32`, `uint64`, `bool`): `SSZType` constants
|
||||
- Slices: Determined from struct tags (`ssz-size` for vectors, `ssz-max` for lists). There is a related [write-up](https://hackmd.io/@junsong/H101DKnwxl) regarding struct tags.
|
||||
- Structs: Analyzed as Containers with field ordering from JSON tags
|
||||
- Pointers: Dereferenced automatically
|
||||
|
||||
2. **Variable-Length Population** - Determines actual sizes at runtime
|
||||
- For lists: Iterates elements, caches sizes for variable-element lists
|
||||
- For containers: Recursively populates variable fields, adjusts offsets
|
||||
- For bitlists: Decodes bit length from bitvector
|
||||
|
||||
3. **Offset Calculation** - Computes byte positions within serialized data
|
||||
- Fixed-size fields: Offset = sum of preceding field sizes
|
||||
- Variable-size fields: Offset stored as 4-byte pointer entries
|
||||
|
||||
### Path Parsing (`path.go`)
|
||||
|
||||
The `ParsePath` function parses path strings with the following rules:
|
||||
|
||||
- **Dot notation**: `.field1.field2` for field access
|
||||
- **Array indexing**: `[0]`, `[42]` for element access
|
||||
- **Length queries**: `len(.field)` for list/vector lengths
|
||||
- **Character set**: Only `[A-Za-z0-9._\[\]\(\)]` allowed
|
||||
|
||||
Example:
|
||||
```go
|
||||
path, _ := ParsePath(".nested.array_field[5].inner_field")
|
||||
// Returns: Path{
|
||||
// Elements: [
|
||||
// PathElement{Name: "nested"},
|
||||
// PathElement{Name: "array_field", Index: <Pointer to uint64(5)>},
|
||||
// PathElement{Name: "inner_field"}
|
||||
// ]
|
||||
// }
|
||||
```
|
||||
|
||||
### Generalized Index Calculation (`generalized_index.go`)
|
||||
|
||||
The generalized index is a tree position identifier. This package follows the [Ethereum consensus-specs](https://github.com/ethereum/consensus-specs/blob/master/ssz/merkle-proofs.md#generalized-merkle-tree-index) to calculate the generalized index.
|
||||
|
||||
### Merkle Proof Generation (`merkle_proof.go`, `proof_collector.go`)
|
||||
|
||||
The `Prove` method generates Merkle proofs using a single-sweep merkleization algorithm:
|
||||
|
||||
#### Algorithm Overview
|
||||
|
||||
**Key Terms:**
|
||||
|
||||
- **Target gindex** (generalized index): The position of the SSZ element you want to prove, expressed as a generalized Merkle tree index. Stored in `Proof.Index`.
|
||||
- Note: The generalized index for root is 1.
|
||||
- **Registered gindices**: The set of tree positions whose node hashes must be captured during merkleization in order to later assemble the proof.
|
||||
- **Sibling node**: The node that shares the same parent as another node.
|
||||
- **Leaf value**: The 32-byte hash of the target node (the node being proven). Stored in `Proof.Leaf`.
|
||||
|
||||
**Phases:**
|
||||
|
||||
1. **Registration Phase** (`addTarget`)
|
||||
> Goal: determine exactly which sibling hashes are needed for the proof.
|
||||
|
||||
- Record the target gindex as the proof target.
|
||||
- Starting from the target node, walk the Merkle tree from the leaf (target gindex) to the root (gindex = 1).
|
||||
- At each step:
|
||||
- Compute and register the sibling gindex (`i XOR 1`) as “must collect”.
|
||||
- Move to the parent (`i = i/2`).
|
||||
- This produces the full set of registered gindices (the sibling nodes on the target-to-root path).
|
||||
|
||||
2. **Merkleization Phase** (`merkleize`)
|
||||
> Goal: recursively merkleize the tree and capture the needed hashes.
|
||||
|
||||
- Recursively traverse the SSZ structure and compute Merkle tree node hashes from leaves to root.
|
||||
- Whenever the traversal computes a node whose gindex is in registered gindices, store that node’s hash for later proof construction.
|
||||
|
||||
3. **Proof Assembly Phase** (`toProof`)
|
||||
> Goal: create the final `fastssz.Proof` object in the correct format and order.
|
||||
|
||||
```go
|
||||
// Proof represents a merkle proof against a general index.
|
||||
type Proof struct {
|
||||
Index int
|
||||
Leaf []byte
|
||||
Hashes [][]byte
|
||||
}
|
||||
```
|
||||
|
||||
- Set `Proof.Index` to the target gindex.
|
||||
- Set `Proof.Leaf` to the 32-byte hash of the target node.
|
||||
- Build `Proof.Hashes` by walking from the target node up to (but not including) the root:
|
||||
- At node `i`, append the stored hash for the sibling (`i XOR 1`).
|
||||
- Move to the parent (`i = i/2`).
|
||||
- The resulting `Proof.Hashes` is ordered from the target level upward, containing one sibling hash per tree level on the path to the root.
|
||||
@@ -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