Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4bf41f6
poc jit builder settings
james-prysm Jul 20, 2026
204cd18
poc additional builder changes
james-prysm Jul 20, 2026
43b7544
fixing auth bug
james-prysm Jul 20, 2026
ef15d45
fixing implicit builder activation and also fixing inheritance of set…
james-prysm Jul 20, 2026
6388b98
self review
james-prysm Jul 20, 2026
c5975aa
reverting builder preference removal
james-prysm Jul 21, 2026
10d4b2e
pocs implementing changes from https://github.com/ethereum/beacon-API…
james-prysm Jul 21, 2026
f7c4e12
handle duplicate urls correctly
james-prysm Jul 21, 2026
feb11d0
error was handled incorrectly updating
james-prysm Jul 21, 2026
f207282
Merge branch 'develop' into jit-builder-settings
james-prysm Jul 21, 2026
9745127
updating error
james-prysm Jul 21, 2026
97eba94
copilot suggestions
james-prysm Jul 21, 2026
47d3aba
make sure builder boost is per builder
james-prysm Jul 21, 2026
93ef8e5
Merge branch 'develop' into jit-builder-settings
james-prysm Jul 21, 2026
b788882
regenerate validator mocks
james-prysm Jul 21, 2026
b67300f
fixing bugs such as dedupe builders on settings, adding missed builde…
james-prysm Jul 22, 2026
c256348
fixing issues around enable builder, api endpoint and making sure emp…
james-prysm Jul 22, 2026
bce03d7
wip
james-prysm Jul 24, 2026
ef51585
keymanager api from 87 to https://github.com/ethereum/keymanager-API…
james-prysm Jul 27, 2026
967744a
adding builder api changes
james-prysm Jul 27, 2026
d65b72b
update based on 88's feedback
james-prysm Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/client/builder/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (c *Client) doWithStatus(ctx context.Context, method string, path string, b

u := c.baseURL.ResolveReference(&url.URL{Path: path})

span.SetAttributes(trace.StringAttribute("url", u.String()),
span.SetAttributes(trace.StringAttribute("url", u.Redacted()),
trace.StringAttribute("method", method))

req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
Expand Down Expand Up @@ -794,7 +794,7 @@ func unexpectedStatusErr(response *http.Response, expected []int) error {
} else {
body = "response body:\n" + string(bodyBytes)
}
msg := fmt.Sprintf("expected=%v, got=%d, url=%s, body=%s", expected, response.StatusCode, response.Request.URL, body)
msg := fmt.Sprintf("expected=%v, got=%d, url=%s, body=%s", expected, response.StatusCode, response.Request.URL.Redacted(), body)

var sentinel error
switch response.StatusCode {
Expand Down
4 changes: 2 additions & 2 deletions api/client/builder/client_gloas.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ func jsonSignedBeaconBlock(sb interfaces.ReadOnlySignedBeaconBlock) ([]byte, err
return json.Marshal(jsonBlock)
}

// SubmitBuilderPreferences submits a proposer's per-builder preferences ahead of the bid request.
// If the builder rejects the SSZ request, it retries once using JSON.
// SubmitBuilderPreferences submits a proposer's per-builder preferences ahead of the bid request
// (builder-specs preferences channel; currently unwired). Falls back to JSON if SSZ is rejected.
func (c *Client) SubmitBuilderPreferences(ctx context.Context, validatorPubkey [48]byte, req *ethpb.BuilderPreferencesRequestV1) error {
if req == nil {
return errors.Wrap(errMalformedRequest, "nil builder preferences request")
Expand Down
1 change: 1 addition & 0 deletions api/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "net/http"

const (
VersionHeader = "Eth-Consensus-Version"
EthBuilderUrlHeader = "Eth-Builder-Url"
ExecutionPayloadBlindedHeader = "Eth-Execution-Payload-Blinded"
ExecutionPayloadValueHeader = "Eth-Execution-Payload-Value"
ConsensusBlockValueHeader = "Eth-Consensus-Block-Value"
Expand Down
32 changes: 14 additions & 18 deletions beacon-chain/builder/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ type BlockBuilder interface {
SubmitBlindedBlock(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error)
SubmitBlindedBlockPostFulu(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) error
GetHeader(ctx context.Context, slot primitives.Slot, parentHash [32]byte, pubKey [48]byte) (builder.SignedBid, error)
GetExecutionPayloadBid(ctx context.Context, slot primitives.Slot, parentHash, parentRoot [32]byte, proposerPubkey [48]byte, auths []*ethpb.SignedRequestAuthV1) ([]PayloadBid, error)
GetExecutionPayloadBid(ctx context.Context, slot primitives.Slot, parentHash, parentRoot [32]byte, proposerPubkey [48]byte, authsByURL map[string]*ethpb.SignedRequestAuthV1) ([]PayloadBid, error)
SubmitSignedBeaconBlock(ctx context.Context, builderURL string, block interfaces.ReadOnlySignedBeaconBlock) error
SubmitBuilderPreferences(ctx context.Context, validatorPubkey [48]byte, req *ethpb.BuilderPreferencesRequestV1) error
SubmitBuilderPreferences(ctx context.Context, builderURL string, validatorPubkey [48]byte, req *ethpb.BuilderPreferencesRequestV1) error
RegisterValidator(ctx context.Context, reg []*ethpb.SignedValidatorRegistrationV1) error
RegistrationByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error)
Configured() bool
Expand Down Expand Up @@ -163,19 +163,15 @@ func (s *Service) SubmitBlindedBlockPostFulu(ctx context.Context, b interfaces.R
}

// Builders are queried concurrently, a failing builder drops only its own bid.
func (s *Service) GetExecutionPayloadBid(ctx context.Context, slot primitives.Slot, parentHash, parentRoot [32]byte, proposerPubkey [48]byte, auths []*ethpb.SignedRequestAuthV1) ([]PayloadBid, error) {
func (s *Service) GetExecutionPayloadBid(ctx context.Context, slot primitives.Slot, parentHash, parentRoot [32]byte, proposerPubkey [48]byte, authsByURL map[string]*ethpb.SignedRequestAuthV1) ([]PayloadBid, error) {
ctx, span := trace.StartSpan(ctx, "builder.GetExecutionPayloadBid")
defer span.End()

byURL := make(map[string]*ethpb.SignedRequestAuthV1, len(auths))
urls := make([]string, 0, len(auths))
for _, a := range auths {
url := string(a.GetMessage().GetData())
if url == "" {
continue
}
if _, ok := byURL[url]; !ok {
byURL[url] = a
// Per builder-specs 5078eab the signed auth data is opaque, so the builder URL
// is provided explicitly by the caller rather than decoded from the auth.
urls := make([]string, 0, len(authsByURL))
for url := range authsByURL {
if url != "" {
urls = append(urls, url)
}
}
Expand All @@ -197,7 +193,7 @@ func (s *Service) GetExecutionPayloadBid(ctx context.Context, slot primitives.Sl
log.WithError(err).WithField("builder", logs.MaskCredentialsLogging(url)).Warn("Could not get builder client")
return
}
bid, err := c.GetExecutionPayloadBid(ctx, slot, parentHash, parentRoot, proposerPubkey, byURL[url])
bid, err := c.GetExecutionPayloadBid(ctx, slot, parentHash, parentRoot, proposerPubkey, authsByURL[url])
if err != nil {
log.WithError(err).WithField("builder", logs.MaskCredentialsLogging(url)).Warn("Could not get builder execution payload bid")
return
Expand Down Expand Up @@ -230,15 +226,15 @@ func (s *Service) SubmitSignedBeaconBlock(ctx context.Context, builderURL string
return c.SubmitSignedBeaconBlock(ctx, b)
}

// Routed to the builder named in the signed request auth.
func (s *Service) SubmitBuilderPreferences(ctx context.Context, validatorPubkey [48]byte, req *ethpb.BuilderPreferencesRequestV1) error {
// SubmitBuilderPreferences forwards a proposer's signed preferences to the builder
// at builderURL (builder-specs preferences channel), ahead of the proposal slot.
func (s *Service) SubmitBuilderPreferences(ctx context.Context, builderURL string, validatorPubkey [48]byte, req *ethpb.BuilderPreferencesRequestV1) error {
ctx, span := trace.StartSpan(ctx, "builder.SubmitBuilderPreferences")
defer span.End()
url := string(req.GetAuth().GetMessage().GetData())
if url == "" {
if builderURL == "" {
return errors.New("builder preferences missing builder url")
}
c, err := s.clientFor(url)
c, err := s.clientFor(builderURL)
if err != nil {
tracing.AnnotateError(span, err)
return err
Expand Down
23 changes: 15 additions & 8 deletions beacon-chain/builder/service_gloas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,22 @@ func newMultiplexService(t *testing.T, clients map[string]*fakeBuilderClient) *S
return s
}

func authsByURL(urls ...string) map[string]*eth.SignedRequestAuthV1 {
m := make(map[string]*eth.SignedRequestAuthV1, len(urls))
for _, u := range urls {
m[u] = authFor(u)
}
return m
}

func TestGetExecutionPayloadBid_FanOutAndDedup(t *testing.T) {
clients := map[string]*fakeBuilderClient{
"http://a": {url: "http://a", bid: bidWithValue(100)},
"http://b": {url: "http://b", bid: bidWithValue(200)},
}
s := newMultiplexService(t, clients)

auths := []*eth.SignedRequestAuthV1{authFor("http://a"), authFor("http://b"), authFor("http://a")}
bids, err := s.GetExecutionPayloadBid(t.Context(), 1, [32]byte{}, [32]byte{}, [48]byte{}, auths)
bids, err := s.GetExecutionPayloadBid(t.Context(), 1, [32]byte{}, [32]byte{}, [48]byte{}, authsByURL("http://a", "http://b"))
require.NoError(t, err)
require.Equal(t, 2, len(bids))

Expand All @@ -83,8 +90,7 @@ func TestGetExecutionPayloadBid_SkipsErrorsAndNil(t *testing.T) {
s := newMultiplexService(t, clients)

// http://nodial has no client; dialing it fails and is skipped.
auths := []*eth.SignedRequestAuthV1{authFor("http://ok"), authFor("http://err"), authFor("http://none"), authFor("http://nodial")}
bids, err := s.GetExecutionPayloadBid(t.Context(), 1, [32]byte{}, [32]byte{}, [48]byte{}, auths)
bids, err := s.GetExecutionPayloadBid(t.Context(), 1, [32]byte{}, [32]byte{}, [48]byte{}, authsByURL("http://ok", "http://err", "http://none", "http://nodial"))
require.NoError(t, err)
require.Equal(t, 1, len(bids))
require.Equal(t, "http://ok", bids[0].BuilderURL)
Expand Down Expand Up @@ -122,17 +128,18 @@ func TestClientFor_SeedsFlagClientAndCachesDials(t *testing.T) {
require.Equal(t, 1, dialed)
}

func TestSubmitBuilderPreferences_DialsPerURL(t *testing.T) {
func TestSubmitBuilderPreferences_DialsExplicitURL(t *testing.T) {
fc := &fakeBuilderClient{url: "http://b"}
s := newMultiplexService(t, map[string]*fakeBuilderClient{"http://b": fc})

req := &eth.BuilderPreferencesRequestV1{
Preferences: &eth.BuilderPreferencesV1{},
Auth: authFor("http://b"),
Auth: authFor("opaque-auth-data"),
}
require.NoError(t, s.SubmitBuilderPreferences(t.Context(), [48]byte{}, req))
require.NoError(t, s.SubmitBuilderPreferences(t.Context(), "http://b", [48]byte{}, req))
require.Equal(t, 1, fc.prefCount)

err := s.SubmitBuilderPreferences(t.Context(), [48]byte{}, &eth.BuilderPreferencesRequestV1{Auth: authFor("")})
// The URL comes from the explicit parameter, never from the opaque auth data.
err := s.SubmitBuilderPreferences(t.Context(), "", [48]byte{}, req)
require.ErrorContains(t, "missing builder url", err)
}
14 changes: 7 additions & 7 deletions beacon-chain/builder/testing/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ type MockBuilderService struct {
RegistrationCache *cache.RegistrationCache
ErrGetHeader error
ErrRegisterValidator error
ErrSubmitBuilderPreferences error
PayloadBid *ethpb.SignedExecutionPayloadBid
PayloadBids []beaconbuilder.PayloadBid
ErrGetExecutionPayloadBid error
ErrSubmitSignedBeaconBlock error
ErrSubmitBuilderPreferences error
Cfg *Config
}

Expand Down Expand Up @@ -123,18 +123,13 @@ func (s *MockBuilderService) RegisterValidator(context.Context, []*ethpb.SignedV
return s.ErrRegisterValidator
}

// SubmitBuilderPreferences for mocking.
func (s *MockBuilderService) SubmitBuilderPreferences(_ context.Context, _ [48]byte, _ *ethpb.BuilderPreferencesRequestV1) error {
return s.ErrSubmitBuilderPreferences
}

// SubmitBlindedBlockPostFulu for mocking.
func (s *MockBuilderService) SubmitBlindedBlockPostFulu(_ context.Context, _ interfaces.ReadOnlySignedBeaconBlock) error {
return s.ErrSubmitBlindedBlockPostFulu
}

// GetExecutionPayloadBid for mocking.
func (s *MockBuilderService) GetExecutionPayloadBid(_ context.Context, _ primitives.Slot, _, _ [32]byte, _ [48]byte, _ []*ethpb.SignedRequestAuthV1) ([]beaconbuilder.PayloadBid, error) {
func (s *MockBuilderService) GetExecutionPayloadBid(_ context.Context, _ primitives.Slot, _, _ [32]byte, _ [48]byte, _ map[string]*ethpb.SignedRequestAuthV1) ([]beaconbuilder.PayloadBid, error) {
if s.PayloadBids != nil {
return s.PayloadBids, s.ErrGetExecutionPayloadBid
}
Expand All @@ -148,3 +143,8 @@ func (s *MockBuilderService) GetExecutionPayloadBid(_ context.Context, _ primiti
func (s *MockBuilderService) SubmitSignedBeaconBlock(_ context.Context, _ string, _ interfaces.ReadOnlySignedBeaconBlock) error {
return s.ErrSubmitSignedBeaconBlock
}

// SubmitBuilderPreferences for mocking.
func (s *MockBuilderService) SubmitBuilderPreferences(_ context.Context, _ string, _ [48]byte, _ *ethpb.BuilderPreferencesRequestV1) error {
return s.ErrSubmitBuilderPreferences
}
11 changes: 10 additions & 1 deletion beacon-chain/rpc/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ func (s *Service) validatorEndpoints(
middleware.AcceptEncodingHeaderHandler(),
},
handler: server.ProduceBlockV4,
methods: []string{http.MethodGet},
methods: []string{http.MethodGet, http.MethodPost},
},
{
template: "/eth/v1/validator/beacon_committee_selections",
Expand All @@ -429,6 +429,15 @@ func (s *Service) validatorEndpoints(
handler: server.BeaconCommitteeSelections,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/builder_preferences/{pubkey}",
name: namespace + ".SubmitBuilderPreferences",
middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
},
handler: server.SubmitBuilderPreferences,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/sync_committee_selections",
name: namespace + ".SyncCommittee Selections",
Expand Down
3 changes: 2 additions & 1 deletion beacon-chain/rpc/endpoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,14 @@ func Test_endpoints(t *testing.T) {
"/eth/v1/validator/duties/sync/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/ptc/{epoch}": {http.MethodPost},
"/eth/v3/validator/blocks/{slot}": {http.MethodGet},
"/eth/v4/validator/blocks/{slot}": {http.MethodGet},
"/eth/v4/validator/blocks/{slot}": {http.MethodGet, http.MethodPost},
"/eth/v1/validator/attestation_data": {http.MethodGet},
"/eth/v2/validator/aggregate_attestation": {http.MethodGet},
"/eth/v2/validator/aggregate_and_proofs": {http.MethodPost},
"/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/beacon_committee_selections": {http.MethodPost},
"/eth/v1/validator/builder_preferences/{pubkey}": {http.MethodPost},
"/eth/v1/validator/sync_committee_selections": {http.MethodPost},
"/eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}": {http.MethodGet},
"/eth/v1/validator/sync_committee_contribution": {http.MethodGet},
Expand Down
11 changes: 6 additions & 5 deletions beacon-chain/rpc/eth/beacon/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ func (s *Server) publishBlindedBlockSSZ(ctx context.Context, w http.ResponseWrit
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
return
}
s.proposeBlock(ctx, w, genericBlock)
s.proposeBlock(ctx, w, r, genericBlock)
}

// decodeBlindedBlockSSZ dispatches to the correct SSZ decoder based on versionHeader.
Expand Down Expand Up @@ -520,7 +520,7 @@ func (s *Server) publishBlindedBlock(ctx context.Context, w http.ResponseWriter,
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
return
}
s.proposeBlock(ctx, w, genericBlock)
s.proposeBlock(ctx, w, r, genericBlock)
}

// decodeBlindedBlockJSON dispatches to the correct JSON decoder based on versionHeader.
Expand Down Expand Up @@ -641,7 +641,7 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
return
}

s.proposeBlock(ctx, w, genericBlock)
s.proposeBlock(ctx, w, r, genericBlock)
}

var sszDecoders = map[string]blockDecoder{
Expand Down Expand Up @@ -809,7 +809,7 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
return
}

s.proposeBlock(ctx, w, genericBlock)
s.proposeBlock(ctx, w, r, genericBlock)
}

var jsonDecoders = map[string]blockDecoder{
Expand Down Expand Up @@ -901,7 +901,8 @@ func broadcastSidecarsIfSupported(ctx context.Context, s *Server, b interfaces.S
}
}

func (s *Server) proposeBlock(ctx context.Context, w http.ResponseWriter, blk *eth.GenericSignedBeaconBlock) {
func (s *Server) proposeBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, blk *eth.GenericSignedBeaconBlock) {
blk.BuilderUrl = r.Header.Get(api.EthBuilderUrlHeader)
_, err := s.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, blk)
Comment on lines +904 to 906
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
Expand Down
2 changes: 2 additions & 0 deletions beacon-chain/rpc/eth/validator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"builder_preferences.go",
"handlers.go",
"handlers_block.go",
"handlers_block_gloas.go",
Expand Down Expand Up @@ -59,6 +60,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"builder_preferences_test.go",
"handlers_block_gloas_test.go",
"handlers_block_test.go",
"handlers_test.go",
Expand Down
Loading
Loading