Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
22 changes: 12 additions & 10 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,14 +632,15 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
return err
}

if len(conf.BuilderURLs) > 0 {
// TODO(gloas): drop this warning once the builder preferences duty authorizes builder entries (#4724).
log.Warn(ctx, "Configured builder URLs are inert for direct gloas builder bids until the builder preferences duty lands (#4724); only P2P and local bids are used", nil,
z.Int("builder_urls", len(conf.BuilderURLs)))
builderCfg := builderConfig(conf)

hosts, err := builderHosts(conf)
if err != nil {
return err
}

fetch, err := fetcher.New(ctx, eth2Cl, builderRegSvc.FeeRecipient, conf.BuilderAPI, graffitiBuilder,
forkSchedule, slotsPerEpoch, builderConfig(conf), featureset.Enabled(featureset.FetchOnlyCommIdx0))
forkSchedule, slotsPerEpoch, builderCfg, featureset.Enabled(featureset.FetchOnlyCommIdx0))
if err != nil {
return err
}
Expand All @@ -654,7 +655,8 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,

dutyDB := dutydb.NewMemDB(retainerFunc("dutydb"))

vapi, err := validatorapi.NewComponent(eth2Cl, allPubSharesByKey, nodeIdx.ShareIdx, builderRegSvc.FeeRecipient, conf.BuilderAPI, lock.TargetGasLimit)
vapi, err := validatorapi.NewComponent(eth2Cl, allPubSharesByKey, nodeIdx.ShareIdx, builderRegSvc.FeeRecipient, conf.BuilderAPI, lock.TargetGasLimit,
validatorapi.WithBuilderHosts(hosts))
if err != nil {
return err
}
Expand Down Expand Up @@ -726,7 +728,7 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
submissionEth2Cl.SetDutiesCache(dutiesCache.ProposerDutiesCache, dutiesCache.ProposerDutiesV2Cache, dutiesCache.AttesterDutiesCache, dutiesCache.SyncCommDutiesCache)
}

broadcaster, err := bcast.New(ctx, submissionEth2Cl, forkSchedule)
broadcaster, err := bcast.New(ctx, submissionEth2Cl, forkSchedule, bcast.WithBuilderConfig(builderCfg))
if err != nil {
return err
}
Expand Down Expand Up @@ -762,7 +764,7 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
fetch.RegisterSyncContributionV2(isync.SyncContributionsSupported)
}

track, err := newTracker(ctx, life, deadlineFunc, peers, eth2Cl)
track, err := newTracker(ctx, life, deadlineFunc, peers, eth2Cl, hosts)
if err != nil {
return err
}
Expand Down Expand Up @@ -886,7 +888,7 @@ func wirePrioritise(ctx context.Context, conf Config, life *lifecycle.Manager, p

// newTracker creates and starts a new tracker instance.
func newTracker(ctx context.Context, life *lifecycle.Manager, deadlineFunc func(duty core.Duty) (time.Time, bool),
peers []p2p.Peer, eth2Cl eth2wrap.Client,
peers []p2p.Peer, eth2Cl eth2wrap.Client, builderHosts []string,
) (core.Tracker, error) {
slotDuration, _, err := eth2wrap.FetchSlotsConfig(ctx, eth2Cl)
if err != nil {
Expand All @@ -910,7 +912,7 @@ func newTracker(ctx context.Context, life *lifecycle.Manager, deadlineFunc func(
return nil, err
}

track := tracker.New(analyser, deleter, peers, trackFrom)
track := tracker.New(analyser, deleter, peers, trackFrom, tracker.WithBuilderHosts(builderHosts))
life.RegisterStart(lifecycle.AsyncBackground, lifecycle.StartTracker, lifecycle.HookFunc(track.Run))

return track, nil
Expand Down
42 changes: 34 additions & 8 deletions app/builderconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (

"github.com/attestantio/go-eth2-client/spec/gloas"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"

"github.com/obolnetwork/charon/eth2util"
)

// builderConfigured returns true if builder URLs are configured, enabling the v2
Expand All @@ -22,19 +24,43 @@ func builderConfigured(conf Config) bool {
// every v4 EPBS proposal request. MinBid and BuilderBoostFactor gate P2P builder bids
// against the locally built payload.
//
// No direct builder entries are emitted, even when builder URLs are configured: each
// BuilderEntry requires a per-proposal-slot, proposer-signed authorization, and the eth2
// client rejects an entry whose auth is missing, which would fail every proposal. Direct
// builder bids therefore stay inert until the builder preferences duty supplies those
// authorizations; the beacon node meanwhile considers P2P and local bids. The builder
// URLs are still distributed to validator clients via the proposer config file and
// surfaced in metrics and peer info.
// TODO(gloas): populate Builders with authorized entries once the builder preferences duty lands (#4724).
// Each configured builder is a direct builder entry without its auth: an entry requires
// the proposer's builder request auth for the proposal slot, which the fetcher adds per
// proposal from the aggregated builder preferences duty, leaving out a builder whose auth
// isn't aggregated.
func builderConfig(conf Config) *gloas.BuilderConfig {
builders := make([]*gloas.BuilderEntry, 0, len(conf.BuilderURLs))
for _, url := range conf.BuilderURLs {
builders = append(builders, &gloas.BuilderEntry{
URL: []byte(url),
BuilderPubkeys: []eth2p0.BLSPubKey{},
MaxExecutionPayment: eth2p0.Gwei(conf.BuilderMaxExecutionPayment),
MinBid: eth2p0.Gwei(conf.BuilderMinBid),
BuilderBoostFactor: conf.BuilderBoostFactor,
})
}

return &gloas.BuilderConfig{
MinBid: eth2p0.Gwei(conf.BuilderMinBid),
BuilderBoostFactor: conf.BuilderBoostFactor,
Builders: builders,
}
}

// builderHosts returns the hosts of the configured builders, the default data of the builder
// request auths validator clients sign for them, in the configured order.
func builderHosts(conf Config) ([]string, error) {
hosts := make([]string, 0, len(conf.BuilderURLs))
for _, url := range conf.BuilderURLs {
host, err := eth2util.BuilderAuthData(url)
if err != nil {
return nil, err
}

hosts = append(hosts, string(host))
}

return hosts, nil
}

// builderConfigHash returns a digest of the canonicalised builder configuration.
Expand Down
28 changes: 24 additions & 4 deletions app/builderconfig_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import (
)

func TestBuilderConfig(t *testing.T) {
// No direct builder entries are emitted even with builder URLs configured: the eth2
// client rejects an entry with a nil auth, which would fail every proposal. MinBid and
// BuilderBoostFactor still apply, gating P2P bids.
// Each configured builder is an entry without its auth: the fetcher adds the aggregated
// builder request auth per proposal. MinBid and BuilderBoostFactor also gate P2P bids.
conf := Config{
BuilderURLs: []string{"https://a.example.com", "https://b.example.com"},
BuilderMinBid: 7,
Expand All @@ -20,9 +19,20 @@ func TestBuilderConfig(t *testing.T) {
}

cfg := builderConfig(conf)
require.Empty(t, cfg.Builders)
require.EqualValues(t, 7, cfg.MinBid)
require.EqualValues(t, 120, cfg.BuilderBoostFactor)
require.Len(t, cfg.Builders, 2)

for i, entry := range cfg.Builders {
require.Equal(t, []byte(conf.BuilderURLs[i]), entry.URL)
require.Nil(t, entry.Auth)
require.Empty(t, entry.BuilderPubkeys)
require.EqualValues(t, 3, entry.MaxExecutionPayment)
require.EqualValues(t, 7, entry.MinBid)
require.EqualValues(t, 120, entry.BuilderBoostFactor)
}

require.Empty(t, builderConfig(Config{BuilderMinBid: 7}).Builders)
}

func TestBuilderConfigured(t *testing.T) {
Expand Down Expand Up @@ -65,3 +75,13 @@ func TestBuilderConfigHash(t *testing.T) {
// An unconfigured node still produces a stable non-empty hash.
require.Len(t, builderConfigHash(Config{}), 32)
}

func TestBuilderHosts(t *testing.T) {
hosts, err := builderHosts(Config{BuilderURLs: []string{"https://Relay-A.example.com:443/path", "http://relay-b.example.com"}})
require.NoError(t, err)
require.Equal(t, []string{"relay-a.example.com", "relay-b.example.com"}, hosts)

hosts, err = builderHosts(Config{})
require.NoError(t, err)
require.Empty(t, hosts)
}
41 changes: 35 additions & 6 deletions app/eth2wrap/eth2wrap_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/eth2wrap/genwrap/genwrap.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading