Skip to content

Commit ebe7bc1

Browse files
authored
Merge pull request #743 from ethpandaops/pk910/fix-gloas-state-transition
statetransition: handle Gloas builder deposits in process_deposit_request
2 parents a076cc3 + 192f38a commit ebe7bc1

1 file changed

Lines changed: 131 additions & 7 deletions

File tree

indexer/beacon/statetransition/operations.go

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"bytes"
55
"slices"
66

7+
"github.com/ethpandaops/dora/indexer/beacon/depositsig"
78
"github.com/ethpandaops/go-eth2-client/spec"
89
"github.com/ethpandaops/go-eth2-client/spec/all"
910
"github.com/ethpandaops/go-eth2-client/spec/altair"
11+
"github.com/ethpandaops/go-eth2-client/spec/bellatrix"
1012
"github.com/ethpandaops/go-eth2-client/spec/capella"
1113
"github.com/ethpandaops/go-eth2-client/spec/electra"
1214
"github.com/ethpandaops/go-eth2-client/spec/gloas"
@@ -60,13 +62,7 @@ func applyExecutionRequests(s *stateAccessor, requests *electra.ExecutionRequest
6062
return
6163
}
6264
for _, deposit := range requests.Deposits {
63-
s.PendingDeposits = append(s.PendingDeposits, &electra.PendingDeposit{
64-
Pubkey: deposit.Pubkey,
65-
WithdrawalCredentials: deposit.WithdrawalCredentials,
66-
Amount: deposit.Amount,
67-
Signature: deposit.Signature,
68-
Slot: s.Slot,
69-
})
65+
processDepositRequest(s, deposit)
7066
}
7167
for _, withdrawal := range requests.Withdrawals {
7268
processWithdrawalRequest(s, withdrawal)
@@ -76,6 +72,134 @@ func applyExecutionRequests(s *stateAccessor, requests *electra.ExecutionRequest
7672
}
7773
}
7874

75+
// builderWithdrawalPrefix is BUILDER_WITHDRAWAL_PREFIX (Gloas): the withdrawal
76+
// credential prefix that designates a builder deposit.
77+
const builderWithdrawalPrefix = 0x03
78+
79+
// processDepositRequest implements process_deposit_request.
80+
//
81+
// Pre-Gloas the request is unconditionally appended to the pending_deposits
82+
// queue. Gloas (EIP-7732) diverts builder deposits out of the queue: a deposit
83+
// for a pubkey that already belongs to a builder — or one carrying the 0x03
84+
// builder credential for a brand-new pubkey — is applied immediately to the
85+
// builder registry and never enters the queue.
86+
//
87+
// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#modified-process_deposit_request
88+
func processDepositRequest(s *stateAccessor, deposit *electra.DepositRequest) {
89+
if s.Version >= spec.DataVersionGloas {
90+
_, isBuilder := findBuilderByPubkey(s, deposit.Pubkey)
91+
isValidator := findValidatorByPubkey(s, deposit.Pubkey) != nil
92+
if isBuilder || (isBuilderWithdrawalCredential(deposit.WithdrawalCredentials) &&
93+
!isValidator && !isPendingValidator(s, deposit.Pubkey)) {
94+
// Apply builder deposits immediately.
95+
applyDepositForBuilder(s, deposit.Pubkey, deposit.WithdrawalCredentials, deposit.Amount, deposit.Signature, s.Slot)
96+
return
97+
}
98+
}
99+
100+
// Add validator deposits to the queue.
101+
s.PendingDeposits = append(s.PendingDeposits, &electra.PendingDeposit{
102+
Pubkey: deposit.Pubkey,
103+
WithdrawalCredentials: deposit.WithdrawalCredentials,
104+
Amount: deposit.Amount,
105+
Signature: deposit.Signature,
106+
Slot: s.Slot,
107+
})
108+
}
109+
110+
// isBuilderWithdrawalCredential implements is_builder_withdrawal_credential (Gloas).
111+
func isBuilderWithdrawalCredential(withdrawalCredentials []byte) bool {
112+
return len(withdrawalCredentials) > 0 && withdrawalCredentials[0] == builderWithdrawalPrefix
113+
}
114+
115+
// findBuilderByPubkey returns the builder registry index for a pubkey, if present.
116+
func findBuilderByPubkey(s *stateAccessor, pubkey phase0.BLSPubKey) (uint64, bool) {
117+
for i, builder := range s.Builders {
118+
if builder.PublicKey == pubkey {
119+
return uint64(i), true
120+
}
121+
}
122+
return 0, false
123+
}
124+
125+
// isPendingValidator implements is_pending_validator (Gloas): reports whether a
126+
// pending deposit with a valid signature is already queued for the given pubkey.
127+
//
128+
// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#new-is_pending_validator
129+
func isPendingValidator(s *stateAccessor, pubkey phase0.BLSPubKey) bool {
130+
depositDomain := depositsig.Domain(s.specs.GenesisForkVersion)
131+
for _, pd := range s.PendingDeposits {
132+
if pd.Pubkey != pubkey {
133+
continue
134+
}
135+
if depositsig.Valid(pd.Pubkey, pd.WithdrawalCredentials, phase0.Gwei(pd.Amount), pd.Signature, depositDomain) {
136+
return true
137+
}
138+
}
139+
return false
140+
}
141+
142+
// applyDepositForBuilder implements apply_deposit_for_builder (Gloas). For an
143+
// existing builder the amount tops up its balance. For a new pubkey a builder is
144+
// registered ONLY if the deposit carries a valid signature (proof-of-possession);
145+
// otherwise the deposit is silently dropped, exactly as the chain does.
146+
//
147+
// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#new-apply_deposit_for_builder
148+
func applyDepositForBuilder(s *stateAccessor, pubkey phase0.BLSPubKey, withdrawalCredentials []byte, amount phase0.Gwei, signature phase0.BLSSignature, slot phase0.Slot) {
149+
if builderIndex, isBuilder := findBuilderByPubkey(s, pubkey); isBuilder {
150+
s.Builders[builderIndex].Balance += amount
151+
return
152+
}
153+
154+
// New builder: only register if the deposit signature is valid.
155+
depositDomain := depositsig.Domain(s.specs.GenesisForkVersion)
156+
if !depositsig.Valid(pubkey, withdrawalCredentials, amount, signature, depositDomain) {
157+
return
158+
}
159+
addBuilderToRegistry(s, pubkey, withdrawalCredentials, amount, slot)
160+
}
161+
162+
// addBuilderToRegistry implements add_builder_to_registry (Gloas). The new builder
163+
// reuses the first slot of a fully-withdrawn (withdrawable, zero-balance) builder,
164+
// or is appended when no such slot exists.
165+
//
166+
// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#new-add_builder_to_registry
167+
func addBuilderToRegistry(s *stateAccessor, pubkey phase0.BLSPubKey, withdrawalCredentials []byte, amount phase0.Gwei, slot phase0.Slot) {
168+
var execAddr bellatrix.ExecutionAddress
169+
if len(withdrawalCredentials) >= 32 {
170+
copy(execAddr[:], withdrawalCredentials[12:])
171+
}
172+
builder := &gloas.Builder{
173+
PublicKey: pubkey,
174+
Version: withdrawalCredentials[0],
175+
ExecutionAddress: execAddr,
176+
Balance: amount,
177+
DepositEpoch: phase0.Epoch(uint64(slot) / s.specs.SlotsPerEpoch),
178+
WithdrawableEpoch: FarFutureEpoch,
179+
}
180+
181+
index := getIndexForNewBuilder(s)
182+
if index == uint64(len(s.Builders)) {
183+
s.Builders = append(s.Builders, builder)
184+
} else {
185+
s.Builders[index] = builder
186+
}
187+
}
188+
189+
// getIndexForNewBuilder implements get_index_for_new_builder (Gloas): the index of
190+
// the first fully-withdrawn builder slot to recycle, or the registry length.
191+
//
192+
// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#new-get_index_for_new_builder
193+
func getIndexForNewBuilder(s *stateAccessor) uint64 {
194+
currentEpoch := s.currentEpoch()
195+
for i, builder := range s.Builders {
196+
if builder.WithdrawableEpoch <= currentEpoch && builder.Balance == 0 {
197+
return uint64(i)
198+
}
199+
}
200+
return uint64(len(s.Builders))
201+
}
202+
79203
// processProposerSlashing processes a proposer slashing.
80204
// https://github.com/ethereum/consensus-specs/blob/master/specs/phase0/beacon-chain.md#proposer-slashings
81205
func processProposerSlashing(s *stateAccessor, slashing *phase0.ProposerSlashing) {

0 commit comments

Comments
 (0)