@@ -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
81205func processProposerSlashing (s * stateAccessor , slashing * phase0.ProposerSlashing ) {
0 commit comments