Skip to content

Commit d7cb48a

Browse files
authored
simulators/eth2/withdrawals: Withdrawals Tests Fixes (#736)
- Fixes obtaining the beacon state from prysm, which only supports /eth/v2/debug/beacon/states/{state_id} where state_id is a slot number. - Fix Capella genesis tests by including the execution genesis as the previous execution payload header in the genesis beacon state. - Fix tests where withdrawals were not noticeable because of a decreasing balance (below the minimum withdraw balance of MAX_EFFECTIVE_BALANCE).
1 parent 500bcd1 commit d7cb48a

File tree

8 files changed

+224
-12
lines changed

8 files changed

+224
-12
lines changed

simulators/eth2/common/clients/beacon.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ const (
3838
PortValidatorAPI = 5000
3939
)
4040

41-
var (
42-
EMPTY_TREE_ROOT = tree.Root{}
43-
)
41+
var EMPTY_TREE_ROOT = tree.Root{}
4442

4543
type BeaconClient struct {
4644
T *hivesim.T
@@ -98,6 +96,11 @@ func (bn *BeaconClient) Start(extraOptions ...hivesim.StartOption) error {
9896
Cli: &http.Client{},
9997
Codec: eth2api.JSONCodec{},
10098
}
99+
bn.T.Logf(
100+
"Started client %s, container: %s",
101+
bn.ClientType,
102+
bn.HiveClient.Container,
103+
)
101104
return nil
102105
}
103106

@@ -223,6 +226,14 @@ func (versionedBlock *VersionedSignedBeaconBlock) ExecutionPayload() (api.Execut
223226
return result, nil
224227
}
225228

229+
func (versionedBlock *VersionedSignedBeaconBlock) Withdrawals() (common.Withdrawals, error) {
230+
switch v := versionedBlock.Data.(type) {
231+
case *capella.SignedBeaconBlock:
232+
return v.Message.Body.ExecutionPayload.Withdrawals, nil
233+
}
234+
return nil, nil
235+
}
236+
226237
func (b *VersionedSignedBeaconBlock) StateRoot() tree.Root {
227238
switch v := b.Data.(type) {
228239
case *phase0.SignedBeaconBlock:
@@ -445,6 +456,20 @@ type VersionedBeaconStateResponse struct {
445456
spec *common.Spec
446457
}
447458

459+
func (vbs *VersionedBeaconStateResponse) Root() tree.Root {
460+
switch state := vbs.Data.(type) {
461+
case *phase0.BeaconState:
462+
return state.HashTreeRoot(vbs.spec, tree.GetHashFn())
463+
case *altair.BeaconState:
464+
return state.HashTreeRoot(vbs.spec, tree.GetHashFn())
465+
case *bellatrix.BeaconState:
466+
return state.HashTreeRoot(vbs.spec, tree.GetHashFn())
467+
case *capella.BeaconState:
468+
return state.HashTreeRoot(vbs.spec, tree.GetHashFn())
469+
}
470+
panic("badly formatted beacon state")
471+
}
472+
448473
func (vbs *VersionedBeaconStateResponse) CurrentVersion() common.Version {
449474
switch state := vbs.Data.(type) {
450475
case *phase0.BeaconState:
@@ -565,6 +590,24 @@ func (vbs *VersionedBeaconStateResponse) LatestExecutionPayloadHeaderHash() tree
565590
panic("badly formatted beacon state")
566591
}
567592

593+
func (vbs *VersionedBeaconStateResponse) NextWithdrawalIndex() (common.WithdrawalIndex, error) {
594+
var wIndex common.WithdrawalIndex
595+
switch state := vbs.Data.(type) {
596+
case *capella.BeaconState:
597+
wIndex = state.NextWithdrawalIndex
598+
}
599+
return wIndex, nil
600+
}
601+
602+
func (vbs *VersionedBeaconStateResponse) NextWithdrawalValidatorIndex() (common.ValidatorIndex, error) {
603+
var wIndex common.ValidatorIndex
604+
switch state := vbs.Data.(type) {
605+
case *capella.BeaconState:
606+
wIndex = state.NextWithdrawalValidatorIndex
607+
}
608+
return wIndex, nil
609+
}
610+
568611
func (vbs *VersionedBeaconStateResponse) NextWithdrawals(
569612
slot common.Slot,
570613
) (common.Withdrawals, error) {

simulators/eth2/common/clients/execution.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ func (en *ExecutionClient) Start(extraOptions ...hivesim.StartOption) error {
123123
opts = append(opts, extraOptions...)
124124

125125
en.HiveClient = en.T.StartClient(en.ClientType, opts...)
126+
en.T.Logf(
127+
"Started client %s, container: %s",
128+
en.ClientType,
129+
en.HiveClient.Container,
130+
)
126131

127132
// Prepare Eth/Engine RPCs
128133
engineRPCAddress, err := en.EngineRPCAddress()

simulators/eth2/common/clients/validator.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ func (vc *ValidatorClient) Start(extraOptions ...hivesim.StartOption) error {
5858
}
5959

6060
vc.HiveClient = vc.T.StartClient(vc.ClientType, opts...)
61+
vc.T.Logf(
62+
"Started client %s, container: %s",
63+
vc.ClientType,
64+
vc.HiveClient.Container,
65+
)
6166
return nil
6267
}
6368

simulators/eth2/common/config/consensus/genesis.go

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,59 @@ func genesisPayloadHeader(
6060
}, nil
6161
}
6262

63+
func genesisPayloadHeaderCapella(
64+
eth1GenesisBlock *types.Block,
65+
spec *common.Spec,
66+
) (*capella.ExecutionPayloadHeader, error) {
67+
extra := eth1GenesisBlock.Extra()
68+
if len(extra) > common.MAX_EXTRA_DATA_BYTES {
69+
return nil, fmt.Errorf(
70+
"extra data is %d bytes, max is %d",
71+
len(extra),
72+
common.MAX_EXTRA_DATA_BYTES,
73+
)
74+
}
75+
if len(eth1GenesisBlock.Transactions()) != 0 {
76+
return nil, fmt.Errorf(
77+
"expected no transactions in genesis execution payload",
78+
)
79+
}
80+
if len(eth1GenesisBlock.Withdrawals()) != 0 {
81+
return nil, fmt.Errorf(
82+
"expected no withdrawals in genesis execution payload",
83+
)
84+
}
85+
86+
baseFee, overflow := uint256.FromBig(eth1GenesisBlock.BaseFee())
87+
if overflow {
88+
return nil, fmt.Errorf("basefee larger than 2^256-1")
89+
}
90+
91+
return &capella.ExecutionPayloadHeader{
92+
ParentHash: common.Root(eth1GenesisBlock.ParentHash()),
93+
FeeRecipient: common.Eth1Address(eth1GenesisBlock.Coinbase()),
94+
StateRoot: common.Bytes32(eth1GenesisBlock.Root()),
95+
ReceiptsRoot: common.Bytes32(eth1GenesisBlock.ReceiptHash()),
96+
LogsBloom: common.LogsBloom(eth1GenesisBlock.Bloom()),
97+
PrevRandao: common.Bytes32{},
98+
BlockNumber: view.Uint64View(eth1GenesisBlock.NumberU64()),
99+
GasLimit: view.Uint64View(eth1GenesisBlock.GasLimit()),
100+
GasUsed: view.Uint64View(eth1GenesisBlock.GasUsed()),
101+
Timestamp: common.Timestamp(eth1GenesisBlock.Time()),
102+
ExtraData: extra,
103+
BaseFeePerGas: view.Uint256View(*baseFee),
104+
BlockHash: common.Root(eth1GenesisBlock.Hash()),
105+
// empty transactions root
106+
TransactionsRoot: common.PayloadTransactionsType(spec).
107+
DefaultNode().
108+
MerkleRoot(tree.GetHashFn()),
109+
// empty withdrawals root
110+
WithdrawalsRoot: common.WithdrawalsType(spec).
111+
DefaultNode().
112+
MerkleRoot(tree.GetHashFn()),
113+
}, nil
114+
}
115+
63116
func createValidators(
64117
spec *common.Spec,
65118
keys []*KeyDetails,
@@ -267,10 +320,10 @@ func BuildBeaconState(
267320
}
268321
}
269322

270-
if st, ok := state.(*bellatrix.BeaconStateView); ok {
271-
// did we hit the TTD at genesis block?
323+
switch st := state.(type) {
324+
case *bellatrix.BeaconStateView:
272325
tdd := uint256.Int(spec.TERMINAL_TOTAL_DIFFICULTY)
273-
embedExecAtGenesis := tdd.ToBig().Cmp(eth1Genesis.Difficulty) < 0
326+
embedExecAtGenesis := tdd.ToBig().Cmp(eth1Genesis.Difficulty) <= 0
274327

275328
var execPayloadHeader *bellatrix.ExecutionPayloadHeader
276329
if embedExecAtGenesis {
@@ -287,6 +340,29 @@ func BuildBeaconState(
287340
execPayloadHeader = new(bellatrix.ExecutionPayloadHeader)
288341
}
289342

343+
if err := st.SetLatestExecutionPayloadHeader(execPayloadHeader); err != nil {
344+
return nil, err
345+
}
346+
case *capella.BeaconStateView:
347+
// did we hit the TTD at genesis block?
348+
tdd := uint256.Int(spec.TERMINAL_TOTAL_DIFFICULTY)
349+
embedExecAtGenesis := tdd.ToBig().Cmp(eth1Genesis.Difficulty) <= 0
350+
351+
var execPayloadHeader *capella.ExecutionPayloadHeader
352+
if embedExecAtGenesis {
353+
execPayloadHeader, err = genesisPayloadHeaderCapella(
354+
eth1GenesisBlock,
355+
spec,
356+
)
357+
if err != nil {
358+
return nil, err
359+
}
360+
} else {
361+
// we didn't build any on the eth1 chain though,
362+
// so we just put the genesis hash here (it could be any block from eth1 chain before TTD that is not ahead of eth2)
363+
execPayloadHeader = new(capella.ExecutionPayloadHeader)
364+
}
365+
290366
if err := st.SetLatestExecutionPayloadHeader(execPayloadHeader); err != nil {
291367
return nil, err
292368
}

simulators/eth2/common/config/execution/execution_config.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,26 @@ func (c ExecutionPreChain) SecondsPerBlock() uint64 {
126126
return 1
127127
}
128128

129+
// A pre-existing chain is imported by the client, and it is not
130+
// expected that the client mines or produces any blocks.
131+
type ExecutionPostMergeGenesis struct{}
132+
133+
func (c ExecutionPostMergeGenesis) Configure(*ExecutionGenesis) error {
134+
return nil
135+
}
136+
137+
func (c ExecutionPostMergeGenesis) HiveParams(node int) hivesim.Params {
138+
return hivesim.Params{}
139+
}
140+
141+
func (c ExecutionPostMergeGenesis) DifficultyPerBlock() *big.Int {
142+
return big.NewInt(0)
143+
}
144+
145+
func (c ExecutionPostMergeGenesis) SecondsPerBlock() uint64 {
146+
return 12
147+
}
148+
129149
type ExecutionCliqueConsensus struct {
130150
CliquePeriod uint64
131151
PrivateKey string

simulators/eth2/withdrawals/helper.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package main
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/ecdsa"
67
"fmt"
78
"math/big"
9+
"sort"
810

911
"github.com/ethereum/go-ethereum/common"
1012
"github.com/ethereum/go-ethereum/core/types"
@@ -76,6 +78,20 @@ type BeaconBlockState struct {
7678

7779
type BeaconCache map[tree.Root]BeaconBlockState
7880

81+
// Clear the cache for when there was a known/expected re-org to query everything again
82+
func (c BeaconCache) Clear() error {
83+
roots := make([]tree.Root, len(c))
84+
i := 0
85+
for s := range c {
86+
roots[i] = s
87+
i++
88+
}
89+
for _, s := range roots {
90+
delete(c, s)
91+
}
92+
return nil
93+
}
94+
7995
func (c BeaconCache) GetBlockStateByRoot(
8096
ctx context.Context,
8197
bc *clients.BeaconClient,
@@ -88,10 +104,17 @@ func (c BeaconCache) GetBlockStateByRoot(
88104
if err != nil {
89105
return BeaconBlockState{}, err
90106
}
91-
s, err := bc.BeaconStateV2(ctx, eth2api.StateIdRoot(b.StateRoot()))
107+
s, err := bc.BeaconStateV2(ctx, eth2api.StateIdSlot(b.Slot()))
92108
if err != nil {
93109
return BeaconBlockState{}, err
94110
}
111+
blockStateRoot := b.StateRoot()
112+
stateRoot := s.Root()
113+
if !bytes.Equal(blockStateRoot[:], stateRoot[:]) {
114+
return BeaconBlockState{}, fmt.Errorf(
115+
"state root missmatch while fetching state",
116+
)
117+
}
95118
both := BeaconBlockState{
96119
VersionedBeaconStateResponse: s,
97120
VersionedSignedBeaconBlock: b,
@@ -128,6 +151,42 @@ func (c BeaconCache) GetBlockStateBySlotFromHeadRoot(
128151
}
129152
}
130153

154+
func PrintWithdrawalHistory(c BeaconCache) error {
155+
slotMap := make(map[beacon.Slot]tree.Root)
156+
slots := make([]beacon.Slot, 0)
157+
for r, s := range c {
158+
slot := s.StateSlot()
159+
slotMap[slot] = r
160+
slots = append(slots, slot)
161+
}
162+
163+
sort.Slice(slots, func(i, j int) bool { return slots[j] > slots[i] })
164+
165+
for _, slot := range slots {
166+
root := slotMap[slot]
167+
s := c[root]
168+
nextWithdrawalIndex, _ := s.NextWithdrawalIndex()
169+
nextWithdrawalValidatorIndex, _ := s.NextWithdrawalValidatorIndex()
170+
fmt.Printf(
171+
"Slot=%d, NextWithdrawalIndex=%d, NextWithdrawalValidatorIndex=%d\n",
172+
slot,
173+
nextWithdrawalIndex,
174+
nextWithdrawalValidatorIndex,
175+
)
176+
fmt.Printf("Withdrawals:\n")
177+
ws, _ := s.Withdrawals()
178+
for i, w := range ws {
179+
fmt.Printf(
180+
"%d: Validator Index: %s, Amount: %d\n",
181+
i,
182+
w.ValidatorIndex,
183+
w.Amount,
184+
)
185+
}
186+
}
187+
return nil
188+
}
189+
131190
// Helper struct to keep track of current status of a validator withdrawal state
132191
type Validator struct {
133192
Index beacon.ValidatorIndex

simulators/eth2/withdrawals/scenarios.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ func (ts BaseWithdrawalsTestSpec) Execute(
165165
// Get the beacon state and verify the credentials were updated
166166
var versionedBeaconState *clients.VersionedBeaconStateResponse
167167
for _, bn := range testnet.BeaconClients().Running() {
168-
versionedBeaconState, err = bn.BeaconStateV2ByBlock(
168+
versionedBeaconState, err = bn.BeaconStateV2(
169169
ctx,
170-
eth2api.BlockHead,
170+
eth2api.StateHead,
171171
)
172172
if err != nil || versionedBeaconState == nil {
173173
t.Logf("WARN: Unable to get latest beacon state: %v", err)
@@ -231,6 +231,7 @@ loop:
231231
for {
232232
select {
233233
case <-slotCtx.Done():
234+
PrintWithdrawalHistory(allValidators[0].BlockStateCache)
234235
t.Fatalf("FAIL: Timeout waiting on all accounts to withdraw")
235236
case <-time.After(time.Duration(testnet.Spec().SECONDS_PER_SLOT) * time.Second):
236237
// Print all info
@@ -256,6 +257,8 @@ loop:
256257
}
257258
}
258259

260+
PrintWithdrawalHistory(allValidators[0].BlockStateCache)
261+
259262
// Lastly check all clients are on the same head
260263
testnet.VerifyELHeads(ctx)
261264

simulators/eth2/withdrawals/specs.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ var (
5959
AltairForkEpoch: common.Big0,
6060
BellatrixForkEpoch: common.Big0,
6161
CapellaForkEpoch: common.Big1,
62-
Eth1Consensus: &el.ExecutionCliqueConsensus{},
62+
Eth1Consensus: &el.ExecutionPostMergeGenesis{},
6363
}
6464

6565
MINIMAL_SLOT_TIME_CLIENTS = []string{
@@ -204,8 +204,9 @@ func (ts BaseWithdrawalsTestSpec) GetValidatorKeys(
204204
}
205205

206206
for index, key := range keys {
207-
// All validators have idiosyncratic balance amounts to identify them
208-
key.ExtraInitialBalance = beacon.Gwei(index+1) + ts.ExtraGwei
207+
// All validators have idiosyncratic balance amounts to identify them.
208+
// Also include a high amount in order to guarantee withdrawals.
209+
key.ExtraInitialBalance = beacon.Gwei((index+1)*1000000) + ts.ExtraGwei
209210

210211
if ts.GenesisExecutionWithdrawalCredentialsShares > 0 &&
211212
(index%ts.GenesisExecutionWithdrawalCredentialsShares) == 0 {

0 commit comments

Comments
 (0)