Skip to content

gloas: add new execution payload bid processing#15638

Merged
terencechain merged 6 commits into
developfrom
process-exec-header
Jan 15, 2026
Merged

gloas: add new execution payload bid processing#15638
terencechain merged 6 commits into
developfrom
process-exec-header

Conversation

@terencechain

@terencechain terencechain commented Aug 27, 2025

Copy link
Copy Markdown
Collaborator

This PR implements process_execution_payload_bid and spec tests

@terencechain terencechain force-pushed the process-exec-header branch 6 times, most recently from 9b74781 to 1f182f1 Compare November 25, 2025 18:02
@terencechain terencechain marked this pull request as ready for review November 25, 2025 18:03
@terencechain terencechain force-pushed the process-exec-header branch 7 times, most recently from 7fdffad to 62b5aba Compare November 25, 2025 18:51
@potuz potuz self-assigned this Nov 26, 2025
return fmt.Errorf("invalid signature format: %w", err)
}

currentEpoch := slots.ToEpoch(bid.Slot())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be from the bid or form the state slot? or do they need to be checked against each other?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we verified bid.Slot() == block.Slot() and in process_block_header, we also verified state.Slot() == block.Slot() so they all are consistent

if amount != 0 {
return fmt.Errorf("self-build amount must be zero, got %d", amount)
}
if wrappedBid.Signature() != common.InfiniteSignature {

@james-prysm james-prysm Dec 4, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be !bytes.Equal(wrappedBid.Signature(),common.InfiniteSignature)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both of them are [96]byte..
wrappedBid.Signature() != common.InfiniteSignature wouldn't work?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah actually i'm probably wrong on this i just usually see bytes.Equal when comparing bytes but checked myself that this should be fine

if err != nil {
return fmt.Errorf("failed to get randao mix: %w", err)
}
if bid.PrevRandao() != [32]byte(randaoMix) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about doing something like

	bidPrevRandao := bid.PrevRandao()
	if !bytes.Equal(bidPrevRandao[:], randaoMix) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any real concern with casting? at this point we know randaoMix is 32 bytes and casting is a bit easier to read / very marginally faster, we also use this [32]byte cast in a bunch of places in prysm already

@james-prysm james-prysm Dec 4, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think similiar to the previous one I was just more used to seeing bytes.Equal comparison because most were slices vs arrays for bytes. but as a follow up when do we [32]byte cast this way vs using bytesutil.ToBytes32( I think we usually use the latter

},
}
slotIndex := params.BeaconConfig().SlotsPerEpoch + (bid.Slot() % params.BeaconConfig().SlotsPerEpoch)
if err := st.SetBuilderPendingPayment(slotIndex, pendingPayment); err != nil {

@james-prysm james-prysm Dec 4, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the underlying value is a slice when you set builder pending payment, do we need any safety checks on slotIndex so that it's not some out of bound index compared to the underlying slice

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it makes sense to bound check in SetBuilderPendingPayment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think i see a bounds check added for this one yet

}

// CopyBuilderPendingPayment creates a deep copy of a builder pending payment.
func CopyBuilderPendingPayment(original *BuilderPendingPayment) *BuilderPendingPayment {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if it's part of another pr but some stuff is missing maybe due to updated gloas proto?

 - copySignedExecutionPayloadBid missing PrevRandao and ExecutionPayment
  - copyPayloadAttestation shallow-copies AggregationBits
  - genExecutionPayloadBidGloas test helper missing those same fields

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll do them in later prs


builderIndex := bid.BuilderIndex()
proposerIndex := block.ProposerIndex()
amount := bid.Value()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between value and executionPayment?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one is in protocol, through consensus,
one is out of protocol, optional

@terencechain terencechain changed the title Add Gloas execution payload header processing gloas: add new execution payload bid processing Jan 5, 2026
@terencechain terencechain force-pushed the process-exec-header branch 3 times, most recently from 00fc4a7 to 0816f23 Compare January 9, 2026 00:05
Add Gloas execution payload header processing and state integration

Implement Gloas fork support in consensus-types/blocks

Add Gloas state fields to beacon state implementation
return true
}

if len(h.payload.ParentBlockHash) != 32 ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checks for prev randao and feerecipient are missing here

james-prysm
james-prysm previously approved these changes Jan 9, 2026

@james-prysm james-prysm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all my comments have been addressed

james-prysm
james-prysm previously approved these changes Jan 12, 2026
@potuz potuz added the blocked label Jan 12, 2026

@potuz potuz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model likes fmt.Errorf("my error %w", err) when most of our code likes errors.Wrap.

Left a few comments about unnecessary copies in getters in the state. We should try to avoid these early in the fork to avoid piling performance issues.

Comment thread beacon-chain/core/gloas/bid.go Outdated
func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyBeaconBlock) error {
signedBid, err := block.Body().SignedExecutionPayloadBid()
if err != nil {
return fmt.Errorf("failed to get signed execution payload bid: %w", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not wrapping the error instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%w in fmt.Errorf wraps an error

return fmt.Errorf("self-build amount must be zero, got %d", amount)
}
if wrappedBid.Signature() != common.InfiniteSignature {
return errors.New("self-build signature must be point at infinity")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return errors.New("self-build signature must be point at infinity")
return errors.New("self-build signature must be the point at infinity")

b.lock.RLock()
defer b.lock.RUnlock()

builder, err := b.builderAtIndex(builderIndex)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that this is an internal beacon state getter that is copying a builder when it doesn't. This applies to others in the state package, even away from Gloas.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in particular I think we should stick to non exported methods to NOT copy the structure or at least agree on a common convention.

b.lock.RLock()
defer b.lock.RUnlock()

builder, err := b.builderAtIndex(builderIndex)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, another copy.

@potuz potuz removed the blocked label Jan 12, 2026

@potuz potuz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass, looks good to me, need a last pass on the last files.

}
func (s stubBlock) HashTreeRootWith(*fastssz.Hasher) error { return nil }

func buildGloasState(t *testing.T, slot primitives.Slot, proposerIdx primitives.ValidatorIndex, builderIdx primitives.BuilderIndex, balance uint64, randao [32]byte, latestHash [32]byte, builderPubkey [48]byte) *state_native.BeaconState {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have these functions here? we typically have a deterministic state builder for tests in the testing package.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that hand crafting the block and state is a better exercise than using a deterministic state. Doing it by hand forces you to think carefully about which fields are required and which are not, rather than taking everything for granted. That said, I may revert back to a deterministic since this is a lot of work

func (s stubBlock) UnmarshalSSZ([]byte) error { return nil }
func (s stubBlock) SizeSSZ() int { return 0 }
func (s stubBlock) Version() int { return s.v }
func (s stubBlock) AsSignRequestObject() (validatorpb.SignRequestObject, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention is that stubBlock and stubBlockBody satisfy the right interfaces? in this case perhaps it's good to add an assert right here that they do.

return false, nil
}

return balance-minBalance >= uint64(bidAmount), nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this passes the linter, I thought gofmt would write this as balance - minBalance

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wrote balance - minBalance and they changed back to balance-minBalance

)

func blockWithSignedExecutionPayloadBid(blockSSZ []byte) (interfaces.SignedBeaconBlock, error) {
var block ethpb.BeaconBlockGloas

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next two functions have either var block ethpb.BeaconBlockGloas or block := &ethpb.BeaconBlockGloas{}. Why the difference?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason, i didnt care too much about being consistent and nit in test, I can stick to one if you want, but i think that's hard to enforce across prysm with different authors

for _, folder := range testFolders {
t.Run(folder.Name(), func(t *testing.T) {
if folder.Name() != "process_execution_payload_bid_self_build_non_zero_value" {
t.Skip("skipping other tests for now")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this? are there no more bid processing tests?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, i wanted to debug one test and forgot to uncomment to test the rest

@terencechain terencechain added this pull request to the merge queue Jan 15, 2026
Merged via the queue into develop with commit a08f185 Jan 15, 2026
19 checks passed
@terencechain terencechain deleted the process-exec-header branch January 15, 2026 11:21
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Gloas Feb 11, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in Gloas Feb 11, 2026
@terencechain terencechain assigned terencechain and unassigned potuz Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants