Trigger Execution Requests In E2E (#14971)

* Trigger Consolidation

* Finally have it working

* Fix Build

* Revert Change

* Fix Context

* Finally have consolidations working

* Get Evaluator Working

* Get Withdrawals Working

* Changelog

* Finally Passes

* New line

* Try again

* fmt

* fmt

* Fix Test
This commit is contained in:
Nishant Das
2025-04-01 14:49:51 +08:00
committed by GitHub
parent a50e981c74
commit e38fdb09a4
13 changed files with 438 additions and 48 deletions

View File

@@ -0,0 +1,3 @@
### Added
- Added the ability for execution requests to be tested in e2e with electra.

View File

@@ -24,6 +24,7 @@ func E2ETestConfig() *BeaconChainConfig {
e2eConfig.GenesisDelay = 10 // 10 seconds so E2E has enough time to process deposits and get started. e2eConfig.GenesisDelay = 10 // 10 seconds so E2E has enough time to process deposits and get started.
e2eConfig.ChurnLimitQuotient = 65536 e2eConfig.ChurnLimitQuotient = 65536
e2eConfig.MaxValidatorsPerWithdrawalsSweep = 128 e2eConfig.MaxValidatorsPerWithdrawalsSweep = 128
e2eConfig.MinPerEpochChurnLimitElectra = 256000000000
// Time parameters. // Time parameters.
e2eConfig.SecondsPerSlot = 10 e2eConfig.SecondsPerSlot = 10
@@ -73,6 +74,7 @@ func E2EMainnetTestConfig() *BeaconChainConfig {
e2eConfig.MinGenesisActiveValidatorCount = 256 e2eConfig.MinGenesisActiveValidatorCount = 256
e2eConfig.GenesisDelay = 25 // 25 seconds so E2E has enough time to process deposits and get started. e2eConfig.GenesisDelay = 25 // 25 seconds so E2E has enough time to process deposits and get started.
e2eConfig.ChurnLimitQuotient = 65536 e2eConfig.ChurnLimitQuotient = 65536
e2eConfig.MinPerEpochChurnLimitElectra = 256000000000
// Time parameters. // Time parameters.
e2eConfig.SecondsPerSlot = 6 e2eConfig.SecondsPerSlot = 6

View File

@@ -6,6 +6,7 @@ import (
"context" "context"
"sync" "sync"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/async" "github.com/prysmaticlabs/prysm/v5/async"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
@@ -171,7 +172,7 @@ func createDepositData(privKey bls.SecretKey, pubKey bls.PublicKey, withExecCred
if withExecCreds { if withExecCreds {
newCredentials := make([]byte, 12) newCredentials := make([]byte, 12)
newCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte newCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
execAddr := bytesutil.ToBytes20(pubKey.Marshal()) execAddr := bytesutil.ToBytes20(hexutil.MustDecode(executionAddress))
depositMessage.WithdrawalCredentials = append(newCredentials, execAddr[:]...) depositMessage.WithdrawalCredentials = append(newCredentials, execAddr[:]...)
} }
sr, err := depositMessage.HashTreeRoot() sr, err := depositMessage.HashTreeRoot()

View File

@@ -184,8 +184,10 @@ func GethTestnetGenesis(genesisTime uint64, cfg *clparams.BeaconChainConfig) *co
Mixhash: common.HexToHash(defaultMixhash), Mixhash: common.HexToHash(defaultMixhash),
Coinbase: common.HexToAddress(defaultCoinbase), Coinbase: common.HexToAddress(defaultCoinbase),
Alloc: types.GenesisAlloc{ Alloc: types.GenesisAlloc{
da.Address: da.Account, da.Address: da.Account,
ma.Address: ma.Account, ma.Address: ma.Account,
params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0},
params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0},
}, },
ParentHash: common.HexToHash(defaultParenthash), ParentHash: common.HexToHash(defaultParenthash),
} }

View File

@@ -23,6 +23,8 @@ import (
var errUnsupportedVersion = errors.New("schema version not supported by PremineGenesisConfig") var errUnsupportedVersion = errors.New("schema version not supported by PremineGenesisConfig")
const executionAddress = "0x878705ba3f8bc32fcf7f4caa1a35e72af65cf766"
type PremineGenesisConfig struct { type PremineGenesisConfig struct {
GenesisTime uint64 GenesisTime uint64
NVals uint64 NVals uint64

View File

@@ -28,7 +28,7 @@ type componentHandler struct {
web3Signer e2etypes.ComponentRunner web3Signer e2etypes.ComponentRunner
bootnode e2etypes.ComponentRunner bootnode e2etypes.ComponentRunner
eth1Miner e2etypes.ComponentRunner eth1Miner e2etypes.ComponentRunner
txGen e2etypes.ComponentRunner txGen *eth1.TransactionGenerator
builders e2etypes.MultipleComponentRunners builders e2etypes.MultipleComponentRunners
eth1Proxy e2etypes.MultipleComponentRunners eth1Proxy e2etypes.MultipleComponentRunners
eth1Nodes e2etypes.MultipleComponentRunners eth1Nodes e2etypes.MultipleComponentRunners

View File

@@ -33,9 +33,11 @@ go_library(
"@com_github_ethereum_go_ethereum//accounts/keystore:go_default_library", "@com_github_ethereum_go_ethereum//accounts/keystore:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//core/types:go_default_library", "@com_github_ethereum_go_ethereum//core/types:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
"@com_github_ethereum_go_ethereum//crypto/kzg4844:go_default_library", "@com_github_ethereum_go_ethereum//crypto/kzg4844:go_default_library",
"@com_github_ethereum_go_ethereum//ethclient:go_default_library", "@com_github_ethereum_go_ethereum//ethclient:go_default_library",
"@com_github_ethereum_go_ethereum//ethclient/gethclient:go_default_library", "@com_github_ethereum_go_ethereum//ethclient/gethclient:go_default_library",
"@com_github_ethereum_go_ethereum//params:go_default_library",
"@com_github_ethereum_go_ethereum//rpc:go_default_library", "@com_github_ethereum_go_ethereum//rpc:go_default_library",
"@com_github_holiman_uint256//:go_default_library", "@com_github_holiman_uint256//:go_default_library",
"@com_github_mariusvanderwijden_fuzzyvm//filler:go_default_library", "@com_github_mariusvanderwijden_fuzzyvm//filler:go_default_library",

View File

@@ -16,15 +16,19 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
gethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/ethereum/go-ethereum/ethclient/gethclient"
gethparams "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/pkg/errors" "github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/crypto/rand" "github.com/prysmaticlabs/prysm/v5/crypto/rand"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/runtime/interop"
e2e "github.com/prysmaticlabs/prysm/v5/testing/endtoend/params" e2e "github.com/prysmaticlabs/prysm/v5/testing/endtoend/params"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -32,14 +36,23 @@ import (
const txCount = 20 const txCount = 20
type txType int
const (
RandomTx txType = iota
ConsolidationTx
WithdrawalTx
)
var fundedAccount *keystore.Key var fundedAccount *keystore.Key
type TransactionGenerator struct { type TransactionGenerator struct {
keystore string keystore string
seed int64 seed int64
started chan struct{} started chan struct{}
cancel context.CancelFunc cancel context.CancelFunc
paused bool paused bool
txGenType txType
} }
func (t *TransactionGenerator) UnderlyingProcess() *os.Process { func (t *TransactionGenerator) UnderlyingProcess() *os.Process {
@@ -49,7 +62,7 @@ func (t *TransactionGenerator) UnderlyingProcess() *os.Process {
} }
func NewTransactionGenerator(keystore string, seed int64) *TransactionGenerator { func NewTransactionGenerator(keystore string, seed int64) *TransactionGenerator {
return &TransactionGenerator{keystore: keystore, seed: seed} return &TransactionGenerator{keystore: keystore, seed: seed, txGenType: RandomTx}
} }
func (t *TransactionGenerator) Start(ctx context.Context) error { func (t *TransactionGenerator) Start(ctx context.Context) error {
@@ -105,10 +118,26 @@ func (t *TransactionGenerator) Start(ctx context.Context) error {
continue continue
} }
backend := ethclient.NewClient(client) backend := ethclient.NewClient(client)
err = SendTransaction(client, mineKey.PrivateKey, f, gasPrice, mineKey.Address.String(), txCount, backend, false) switch t.txGenType {
if err != nil { case ConsolidationTx:
return err err = SendConsolidationTransaction(mineKey.PrivateKey, gasPrice, backend)
if err != nil {
return err
}
case WithdrawalTx:
err = SendWithdrawalTransaction(mineKey.PrivateKey, newKey.PrivateKey, gasPrice, backend)
if err != nil {
return err
}
case RandomTx:
err = SendTransaction(client, mineKey.PrivateKey, f, gasPrice, mineKey.Address.String(), txCount, backend, false)
if err != nil {
return err
}
default:
logrus.Warnf("Unknown transaction type: %v", t.txGenType)
} }
backend.Close() backend.Close()
} }
} }
@@ -219,6 +248,195 @@ func SendTransaction(client *rpc.Client, key *ecdsa.PrivateKey, f *filler.Filler
return nil return nil
} }
func SendConsolidationTransaction(key *ecdsa.PrivateKey, gasPrice *big.Int, backend *ethclient.Client) error {
totalCreds := e2e.TestParams.NumberOfExecutionCreds
_, pKeys, err := interop.DeterministicallyGenerateKeys(0, totalCreds)
if err != nil {
return err
}
compoundedKey := pKeys[len(pKeys)-1].Marshal()
// Create compounding credentials
if err := createAndSendConsolidation(compoundedKey, compoundedKey, key, gasPrice, backend); err != nil {
return err
}
for _, k := range pKeys {
if err := createAndSendConsolidation(k.Marshal(), compoundedKey, key, gasPrice, backend); err != nil {
return err
}
}
// Junk Requests
for i := 0; i < 2; i++ {
sourcePubkey := [48]byte{byte(i), 0xFF, 0x34, 0xEE}
targetPubkey := compoundedKey
if err := createAndSendConsolidation(sourcePubkey[:], targetPubkey, key, gasPrice, backend); err != nil {
return err
}
}
return nil
}
func createAndSendConsolidation(sourceKey, targetKey []byte, key *ecdsa.PrivateKey, gasPrice *big.Int, backend *ethclient.Client) error {
publicKey := key.Public().(*ecdsa.PublicKey)
fromAddress := gethCrypto.PubkeyToAddress(*publicKey)
// Get nonce
nonce, err := backend.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
return err
}
chainid, err := backend.ChainID(context.Background())
if err != nil {
return err
}
gasLimit := uint64(200000)
sourcePubkey := sourceKey
targetPubkey := targetKey
consolidationData := []byte{}
consolidationData = append(consolidationData, sourcePubkey...)
consolidationData = append(consolidationData, targetPubkey...)
ret, err := backend.CallContract(context.Background(), ethereum.CallMsg{To: &gethparams.ConsolidationQueueAddress}, nil)
if err != nil {
return errors.Wrapf(err, "%s", string(ret))
}
fee := new(big.Int).SetBytes(ret)
fee = fee.Mul(fee, big.NewInt(2))
// Create transaction
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: &gethparams.ConsolidationQueueAddress,
Value: fee,
Gas: gasLimit,
GasPrice: gasPrice,
Data: consolidationData,
})
// Sign transaction
signedTx, err := types.SignTx(tx, types.NewCancunSigner(chainid), key)
if err != nil {
return err
}
return backend.SendTransaction(context.Background(), signedTx)
}
func SendWithdrawalTransaction(key, newKey *ecdsa.PrivateKey, gasPrice *big.Int, backend *ethclient.Client) error {
totalCreds := e2e.TestParams.NumberOfExecutionCreds
_, pKeys, err := interop.DeterministicallyGenerateKeys(0, totalCreds)
if err != nil {
return err
}
compoundedKey := pKeys[len(pKeys)-1].Marshal()
_, invalidWithdrawalKeys, err := interop.DeterministicallyGenerateKeys(totalCreds, totalCreds+4)
if err != nil {
return err
}
publicKey := key.Public().(*ecdsa.PublicKey)
fromAddress := gethCrypto.PubkeyToAddress(*publicKey)
nonce, err := backend.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
return err
}
var withdrawalTxs []*types.Transaction
// Create Withdrawal for compounded key.
tx, err := createWithdrawal(compoundedKey, 0, nonce, key, gasPrice, backend)
if err != nil {
return err
}
withdrawalTxs = append(withdrawalTxs, tx)
nonce++
rGen := rand.NewDeterministicGenerator()
for _, k := range pKeys {
tx, err := createWithdrawal(k.Marshal(), uint64(rGen.Int63n(32000000000)), nonce, key, gasPrice, backend)
if err != nil {
return err
}
withdrawalTxs = append(withdrawalTxs, tx)
nonce++
}
// Junk Requests
for _, k := range invalidWithdrawalKeys {
tx, err := createWithdrawal(k.Marshal(), uint64(rGen.Int63n(32000000000)), nonce, newKey, gasPrice, backend)
if err != nil {
return err
}
withdrawalTxs = append(withdrawalTxs, tx)
nonce++
}
currExecHead := uint64(0)
// Batch And Send Withdrawals
for len(withdrawalTxs) > 0 {
currBlock, err := backend.BlockNumber(context.Background())
if err != nil {
return err
}
if currBlock > currExecHead {
currExecHead = currBlock
maxWithdrawalPerPayload := params.BeaconConfig().MaxWithdrawalRequestsPerPayload
if maxWithdrawalPerPayload > uint64(len(withdrawalTxs)) {
maxWithdrawalPerPayload = uint64(len(withdrawalTxs))
}
for _, tx := range withdrawalTxs[:maxWithdrawalPerPayload] {
if err := backend.SendTransaction(context.Background(), tx); err != nil {
return err
}
}
// Shift slice to only have unsent transactions
withdrawalTxs = withdrawalTxs[maxWithdrawalPerPayload:]
time.Sleep(2 * time.Second)
}
}
return nil
}
func createWithdrawal(sourceKey []byte, amount, nonce uint64, key *ecdsa.PrivateKey, gasPrice *big.Int, backend *ethclient.Client) (*types.Transaction, error) {
chainid, err := backend.ChainID(context.Background())
if err != nil {
return nil, err
}
gasLimit := uint64(200000)
withdrawalData := []byte{}
withdrawalData = append(withdrawalData, sourceKey...)
withdrawalData = append(withdrawalData, bytesutil.Uint64ToBytesBigEndian(amount)...)
ret, err := backend.CallContract(context.Background(), ethereum.CallMsg{To: &gethparams.WithdrawalQueueAddress}, nil)
if err != nil {
return nil, errors.Wrapf(err, "%s", string(ret))
}
fee := new(big.Int).SetBytes(ret)
fee = fee.Mul(fee, big.NewInt(2))
// Create transaction
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: &gethparams.WithdrawalQueueAddress,
Value: fee,
Gas: gasLimit,
GasPrice: gasPrice,
Data: withdrawalData,
})
// Sign transaction
signedTx, err := types.SignTx(tx, types.NewCancunSigner(chainid), key)
if err != nil {
return nil, err
}
return signedTx, nil
}
func (t *TransactionGenerator) SetTxType(typ txType) {
t.txGenType = typ
}
// Pause pauses the component and its underlying process. // Pause pauses the component and its underlying process.
func (t *TransactionGenerator) Pause() error { func (t *TransactionGenerator) Pause() error {
t.paused = true t.paused = true

View File

@@ -73,17 +73,18 @@ func e2eMinimal(t *testing.T, cfg *params.BeaconChainConfig, cfgo ...types.E2ECo
"--enable-tracing", "--enable-tracing",
"--trace-sample-fraction=1.0", "--trace-sample-fraction=1.0",
}, },
ValidatorFlags: []string{}, ValidatorFlags: []string{},
EpochsToRun: uint64(epochsToRun), EpochsToRun: uint64(epochsToRun),
TestSync: true, TestSync: true,
TestFeature: true, TestFeature: true,
TestDeposits: true, TestDeposits: true,
UsePrysmShValidator: false, UsePrysmShValidator: false,
UsePprof: true, UsePprof: true,
TracingSinkEndpoint: tracingEndpoint, TestExecutionRequests: true,
Evaluators: evals, TracingSinkEndpoint: tracingEndpoint,
EvalInterceptor: defaultInterceptor, Evaluators: evals,
Seed: int64(seed), EvalInterceptor: defaultInterceptor,
Seed: int64(seed),
} }
for _, o := range cfgo { for _, o := range cfgo {
o(testConfig) o(testConfig)
@@ -149,18 +150,19 @@ func e2eMainnet(t *testing.T, usePrysmSh, useMultiClient bool, cfg *params.Beaco
"--enable-tracing", "--enable-tracing",
"--trace-sample-fraction=1.0", "--trace-sample-fraction=1.0",
}, },
ValidatorFlags: []string{}, ValidatorFlags: []string{},
EpochsToRun: uint64(epochsToRun), EpochsToRun: uint64(epochsToRun),
TestSync: true, TestSync: true,
TestFeature: true, TestFeature: true,
TestDeposits: true, TestDeposits: true,
UseFixedPeerIDs: true, UseFixedPeerIDs: true,
UsePrysmShValidator: usePrysmSh, UsePrysmShValidator: usePrysmSh,
UsePprof: true, TestExecutionRequests: true,
TracingSinkEndpoint: tracingEndpoint, UsePprof: true,
Evaluators: evals, TracingSinkEndpoint: tracingEndpoint,
EvalInterceptor: defaultInterceptor, Evaluators: evals,
Seed: int64(seed), EvalInterceptor: defaultInterceptor,
Seed: int64(seed),
} }
for _, o := range cfgo { for _, o := range cfgo {
o(testConfig) o(testConfig)

View File

@@ -497,13 +497,15 @@ func (r *testRunner) defaultEndToEndRun() error {
require.NoError(t, err) require.NoError(t, err)
tickingStartTime := helpers.EpochTickerStartTime(genesis) tickingStartTime := helpers.EpochTickerStartTime(genesis)
ec := e2etypes.NewEvaluationContext(r.depositor.History()) ec, err := e2etypes.NewEvaluationContext(r.depositor.History(), e2e.TestParams.NumberOfExecutionCreds)
require.NoError(t, err)
// Run assigned evaluators. // Run assigned evaluators.
if err := r.runEvaluators(ec, conns, tickingStartTime); err != nil { if err := r.runEvaluators(ec, conns, tickingStartTime); err != nil {
return errors.Wrap(err, "one or more evaluators failed") return errors.Wrap(err, "one or more evaluators failed")
} }
// Test execution request processing in electra. // Test execution request processing in electra.
if r.config.TestDeposits && params.ElectraEnabled() { if r.config.TestDeposits && r.config.TestExecutionRequests && params.ElectraEnabled() {
if err := r.comHandler.txGen.Pause(); err != nil { if err := r.comHandler.txGen.Pause(); err != nil {
r.t.Error(err) r.t.Error(err)
} }
@@ -516,6 +518,33 @@ func (r *testRunner) defaultEndToEndRun() error {
} }
} }
if r.config.TestExecutionRequests && params.ElectraEnabled() {
// Test Consolidation Transactions
r.comHandler.txGen.SetTxType(eth1.ConsolidationTx)
// Wait For an epoch before running evaluator
secondsPerEpoch := time.Duration(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
waitForSync := secondsPerEpoch * time.Second
time.Sleep(waitForSync)
for _, evaluator := range []e2etypes.Evaluator{ev.ValidatorsHaveConsolidated} {
t.Run(evaluator.Name, func(t *testing.T) {
assert.NoError(t, evaluator.Evaluation(ec, conns...), "Evaluation failed for sync node")
})
}
// Test Withdrawal Transactions
r.comHandler.txGen.SetTxType(eth1.WithdrawalTx)
// Wait For an epoch before running evaluator
time.Sleep(waitForSync)
for _, evaluator := range []e2etypes.Evaluator{ev.ValidatorsHaveWithdrawnViaExecution} {
t.Run(evaluator.Name, func(t *testing.T) {
assert.NoError(t, evaluator.Evaluation(ec, conns...), "Evaluation failed for sync node")
})
}
r.comHandler.txGen.SetTxType(eth1.RandomTx)
}
index := e2e.TestParams.BeaconNodeCount + e2e.TestParams.LighthouseBeaconNodeCount index := e2e.TestParams.BeaconNodeCount + e2e.TestParams.LighthouseBeaconNodeCount
if config.TestSync { if config.TestSync {
if err := r.testBeaconChainSync(ctx, g, conns, tickingStartTime, bootNode.ENR(), eth1Miner.ENR()); err != nil { if err := r.testBeaconChainSync(ctx, g, conns, tickingStartTime, bootNode.ENR(), eth1Miner.ENR()); err != nil {
@@ -596,7 +625,8 @@ func (r *testRunner) scenarioRun() error {
require.NoError(t, err) require.NoError(t, err)
tickingStartTime := helpers.EpochTickerStartTime(genesis) tickingStartTime := helpers.EpochTickerStartTime(genesis)
ec := e2etypes.NewEvaluationContext(r.depositor.History()) ec, err := e2etypes.NewEvaluationContext(r.depositor.History(), e2e.TestParams.NumberOfExecutionCreds)
require.NoError(t, err)
// Run assigned evaluators. // Run assigned evaluators.
return r.runEvaluators(ec, conns, tickingStartTime) return r.runEvaluators(ec, conns, tickingStartTime)
} }

View File

@@ -110,6 +110,26 @@ var ValidatorsHaveWithdrawn = e2etypes.Evaluator{
Evaluation: validatorsAreWithdrawn, Evaluation: validatorsAreWithdrawn,
} }
// ValidatorsHaveConsolidated checks the beacon state for the consolidated validator and ensures it is exited.
var ValidatorsHaveConsolidated = e2etypes.Evaluator{
Name: "validators_have_consolidated_%d",
Policy: func(currentEpoch primitives.Epoch) bool {
fEpoch := params.BeaconConfig().ElectraForkEpoch
return policies.OnwardsNthEpoch(fEpoch)(currentEpoch)
},
Evaluation: validatorsHaveBeenConsolidated,
}
// ValidatorsHaveWithdrawnViaExecution checks the beacon state for the compounded validator and makes sure it is withdrawn.
var ValidatorsHaveWithdrawnViaExecution = e2etypes.Evaluator{
Name: "validators_have_withdrawn_with_execution_%d",
Policy: func(currentEpoch primitives.Epoch) bool {
fEpoch := params.BeaconConfig().ElectraForkEpoch
return policies.OnwardsNthEpoch(fEpoch)(currentEpoch)
},
Evaluation: validatorsHaveBeenWithdrawnWithExecution,
}
// ValidatorsVoteWithTheMajority verifies whether validator vote for eth1data using the majority algorithm. // ValidatorsVoteWithTheMajority verifies whether validator vote for eth1data using the majority algorithm.
var ValidatorsVoteWithTheMajority = e2etypes.Evaluator{ var ValidatorsVoteWithTheMajority = e2etypes.Evaluator{
Name: "validators_vote_with_the_majority_%d", Name: "validators_vote_with_the_majority_%d",
@@ -438,7 +458,7 @@ func proposeVoluntaryExit(ec *e2etypes.EvaluationContext, conns ...*grpc.ClientC
// Send an exit for a non-exited validator. // Send an exit for a non-exited validator.
for i := 0; i < numOfExits; { for i := 0; i < numOfExits; {
randIndex := primitives.ValidatorIndex(rand.Uint64() % params.BeaconConfig().MinGenesisActiveValidatorCount) randIndex := primitives.ValidatorIndex(rand.Uint64() % params.BeaconConfig().MinGenesisActiveValidatorCount)
if ec.ExitedVals[bytesutil.ToBytes48(privKeys[randIndex].PublicKey().Marshal())] { if ec.ExitedVals[bytesutil.ToBytes48(privKeys[randIndex].PublicKey().Marshal())] || ec.ValidExecutionCredentials[[48]byte(privKeys[randIndex].PublicKey().Marshal())] {
continue continue
} }
if err := sendExit(randIndex); err != nil { if err := sendExit(randIndex); err != nil {
@@ -697,3 +717,98 @@ func validatorsAreWithdrawn(ec *e2etypes.EvaluationContext, conns ...*grpc.Clien
} }
return nil return nil
} }
func validatorsHaveBeenConsolidated(ec *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error {
conn := conns[0]
beaconClient := ethpb.NewBeaconChainClient(conn)
debugClient := ethpb.NewDebugClient(conn)
ctx := context.Background()
chainHead, err := beaconClient.GetChainHead(ctx, &emptypb.Empty{})
if err != nil {
return errors.Wrap(err, "could not get chain head")
}
stObj, err := debugClient.GetBeaconState(ctx, &ethpb.BeaconStateRequest{QueryFilter: &ethpb.BeaconStateRequest_Slot{Slot: chainHead.HeadSlot}})
if err != nil {
return errors.Wrap(err, "could not get state object")
}
versionedMarshaler, err := detect.FromState(stObj.Encoded)
if err != nil {
return errors.Wrap(err, "could not get state marshaler")
}
st, err := versionedMarshaler.UnmarshalBeaconState(stObj.Encoded)
if err != nil {
return errors.Wrap(err, "could not get state")
}
var compoundingVal int
for pubkey := range ec.ValidExecutionCredentials {
if ec.ExitedVals[pubkey] {
continue
}
valIdx, ok := st.ValidatorIndexByPubkey(pubkey)
if !ok {
return errors.Errorf("pubkey %#x does not exist in our state", pubkey)
}
val, err := st.ValidatorAtIndexReadOnly(valIdx)
if err != nil {
return err
}
if val.HasCompoundingWithdrawalCredentials() {
compoundingVal++
continue
}
if val.ExitEpoch() == params.BeaconConfig().FarFutureEpoch {
return errors.Errorf("validator was not exited after consolidation, its exit epoch is %d", val.ExitEpoch())
}
}
if compoundingVal != 1 {
return errors.Errorf("no compounding validators observed, wanted 1 but got %d", compoundingVal)
}
return nil
}
func validatorsHaveBeenWithdrawnWithExecution(ec *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error {
conn := conns[0]
beaconClient := ethpb.NewBeaconChainClient(conn)
debugClient := ethpb.NewDebugClient(conn)
ctx := context.Background()
chainHead, err := beaconClient.GetChainHead(ctx, &emptypb.Empty{})
if err != nil {
return errors.Wrap(err, "could not get chain head")
}
stObj, err := debugClient.GetBeaconState(ctx, &ethpb.BeaconStateRequest{QueryFilter: &ethpb.BeaconStateRequest_Slot{Slot: chainHead.HeadSlot}})
if err != nil {
return errors.Wrap(err, "could not get state object")
}
versionedMarshaler, err := detect.FromState(stObj.Encoded)
if err != nil {
return errors.Wrap(err, "could not get state marshaler")
}
st, err := versionedMarshaler.UnmarshalBeaconState(stObj.Encoded)
if err != nil {
return errors.Wrap(err, "could not get state")
}
for pubkey := range ec.ValidExecutionCredentials {
if ec.ExitedVals[pubkey] {
continue
}
valIdx, ok := st.ValidatorIndexByPubkey(pubkey)
if !ok {
return errors.Errorf("pubkey %#x does not exist in our state", pubkey)
}
val, err := st.ValidatorAtIndexReadOnly(valIdx)
if err != nil {
return err
}
if val.HasCompoundingWithdrawalCredentials() {
if val.ExitEpoch() == params.BeaconConfig().FarFutureEpoch {
return errors.Errorf("validator was not exited after withdrawal, its exit epoch is %d", val.ExitEpoch())
}
}
}
return nil
}

View File

@@ -13,6 +13,7 @@ go_library(
deps = [ deps = [
"//config/params:go_default_library", "//config/params:go_default_library",
"//consensus-types/primitives:go_default_library", "//consensus-types/primitives:go_default_library",
"//runtime/interop:go_default_library",
"//runtime/version:go_default_library", "//runtime/version:go_default_library",
"@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//:go_default_library",
], ],

View File

@@ -8,6 +8,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/runtime/interop"
"github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/runtime/version"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@@ -71,6 +72,7 @@ type E2EConfig struct {
UseValidatorCrossClient bool UseValidatorCrossClient bool
UseBeaconRestApi bool UseBeaconRestApi bool
UseBuilder bool UseBuilder bool
TestExecutionRequests bool
EpochsToRun uint64 EpochsToRun uint64
Seed int64 Seed int64
TracingSinkEndpoint string TracingSinkEndpoint string
@@ -129,18 +131,28 @@ type DepositBalancer interface {
// EvaluationContext allows for additional data to be provided to evaluators that need extra state. // EvaluationContext allows for additional data to be provided to evaluators that need extra state.
type EvaluationContext struct { type EvaluationContext struct {
DepositBalancer DepositBalancer
ExitedVals map[[48]byte]bool ExitedVals map[[48]byte]bool
SeenVotes map[primitives.Slot][]byte SeenVotes map[primitives.Slot][]byte
ExpectedEth1DataVote []byte ExpectedEth1DataVote []byte
ValidExecutionCredentials map[[48]byte]bool
} }
// NewEvaluationContext handles initializing internal datastructures (like maps) provided by the EvaluationContext. // NewEvaluationContext handles initializing internal datastructures (like maps) provided by the EvaluationContext.
func NewEvaluationContext(d DepositBalancer) *EvaluationContext { func NewEvaluationContext(d DepositBalancer, numExecCreds uint64) (*EvaluationContext, error) {
return &EvaluationContext{ _, pkeys, err := interop.DeterministicallyGenerateKeys(0, numExecCreds)
DepositBalancer: d, if err != nil {
ExitedVals: make(map[[48]byte]bool), return nil, err
SeenVotes: make(map[primitives.Slot][]byte),
} }
execMap := make(map[[48]byte]bool)
for _, k := range pkeys {
execMap[[48]byte(k.Marshal())] = true
}
return &EvaluationContext{
DepositBalancer: d,
ExitedVals: make(map[[48]byte]bool),
SeenVotes: make(map[primitives.Slot][]byte),
ValidExecutionCredentials: execMap,
}, nil
} }
// ComponentRunner defines an interface via which E2E component's configuration, execution and termination is managed. // ComponentRunner defines an interface via which E2E component's configuration, execution and termination is managed.