Skip to content

Enforce per-builder bid limits from the block request - #17374

Merged
terencechain merged 4 commits into
developfrom
builder-bid-enforcement
Aug 21, 2026
Merged

terencechain merged 4 commits into
developfrom
builder-bid-enforcement

Conversation

@terencechain

@terencechain terencechain commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator
  • Builder-API bids are checked against their own entry's max execution payment, min bid, boost factor, and builder pubkeys
  • P2P bids use the config-level min bid and boost, payments are trusted only from entries naming the builder key
  • Removes the per-pubkey maxExecutionPayments map, the preferences push now sends each builder its own cap

Last of the stack replacing #17124, on top of #17373.

@github-project-automation github-project-automation Bot moved this to Unassigned in Gloas Aug 18, 2026
@terencechain
terencechain force-pushed the builder-bid-enforcement branch 2 times, most recently from 71936ec to db8cd9e Compare August 18, 2026 18:26
@terencechain
terencechain force-pushed the builder-bid-enforcement branch 2 times, most recently from 589bfe2 to 25af1f9 Compare August 18, 2026 21:28
Base automatically changed from builder-entries-wire to develop August 19, 2026 15:16
var bestBid *ethpb.SignedExecutionPayloadBid
bestValue := primitives.WeiToGwei(local.Bid)
var bestEffective primitives.Gwei
bestBoosted := primitives.WeiToGwei(local.Bid)

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 this is spec compliant for small bids under 100 gwei.

 t.Run("max boost prefers a sub-100-gwei bid over local", func(t *testing.T) {
    // builder_boost_factor=2**64-1 MUST mean "prefer this builder unless
    // unviable" for any nonzero bid, including one below 100 Gwei.
    win := &winningBuilderBid{
      bid:   newBid(99, 0, builderIdx),
      entry: &ethpb.BuilderEntry{MaxExecutionPayment: 0, BuilderBoostFactor: math.MaxUint64},
    }
    got, src, _ := bestBid(nil, localWithGwei(1), nil, win, nil)
    require.NotNil(t, got)
    require.Equal(t, bidSourceBuilderAPI, src)
  })

  t.Run("boost applies the full value below 100 gwei granularity", func(t *testing.T) {
    // 199 boosted by 50% is 199*50/100 = 99, which beats local 75.
    win := &winningBuilderBid{
      bid:   newBid(199, 0, builderIdx),
      entry: &ethpb.BuilderEntry{MaxExecutionPayment: 0, BuilderBoostFactor: 50},
    }
    got, src, _ := bestBid(nil, localWithGwei(75), nil, win, nil)
    require.NotNil(t, got)
    require.Equal(t, bidSourceBuilderAPI, src)
  })

these tests fail ( let me know if you think this is wrong)

@terencechain
terencechain force-pushed the builder-bid-enforcement branch 2 times, most recently from 019d039 to d742dfe Compare August 19, 2026 19:55
}
bool is_blinded = 100;
string payload_value = 101;
string builder_url = 102;

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.

that's unfortunate we needed to add this here, will be annoying to change down the road but ok with it for now i suppose

func (vs *Server) setExecutionPayloadBid(
ctx context.Context,
sBlk interfaces.SignedBeaconBlock,
head state.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.

do we need to pass the head state around like this or just extract what we need? i see head state introduced in several areas here. more of a nit than anything

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 head state is better than passing a pubkey lookup closure

@@ -51,9 +55,10 @@ func (s bidSource) String() string {
func (vs *Server) setExecutionPayloadBid(

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.

This has a ton of parameters, wondering if it's better to have some wrapper object now or not

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 is fine, it's an internal function with exactly one caller

}
}

genericSignedBlock.BuilderUrl = b.BuilderUrl

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.

because of this there's a potential to nil pointer I believe...

we need to do something like

default:
log.Errorf("Unsupported block version %s", version.String(blk.Version()))
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
return
}

otherwise log will fall through and cause a nil pointer here, you could also set it since it's trying to set it for be fore gloas?

you could also have a nil check gate

@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.

LGTM, will have a follow up pr for submit builder preferences timing

@james-prysm
james-prysm force-pushed the builder-bid-enforcement branch from b4100ee to 6235511 Compare August 20, 2026 21:46
@terencechain
terencechain added this pull request to the merge queue Aug 20, 2026
Merged via the queue into develop with commit a0a4b7f Aug 21, 2026
27 checks passed
@terencechain
terencechain deleted the builder-bid-enforcement branch August 21, 2026 00:43
@github-project-automation github-project-automation Bot moved this from Unassigned to Done in Gloas Aug 21, 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.

2 participants