Skip to content

Commit ff8ee84

Browse files
james-prysmsyjn99
andauthored
proposer settings updates for proposer preferences validator client side (OffchainLabs#16762)
**What type of PR is this?** Bug fix **What does this PR do? Why is it needed?** - **New schema version**: `ProposerSettingsPayload.version = 2`. Existing v1/unset files keep working — the loader preserves them as-is, but a deprecation warning is logged at startup when gloas is scheduled on the network. The validator runtime upgrades v1 → v2 in place (`Settings.UpgradeToV2`) once gloas is **active** (`currentEpoch >= GloasForkEpoch`), via `upgradeProposerSettingsToV2` called from `PushProposerSettings` / `submitProposerPreferences`: `builder.gas_limit` is promoted to the top-level `gas_limit` (default config **and** per-validator entries, unless a top-level value is already set), all builder blocks are dropped, and the upgraded settings are persisted to the DB. The upgrade is deliberately deferred until the fork — not earlier — because the pre-gloas relay-registration path still reads the builder block. Settings already on v2 are never rewritten. - **`gas_limit` is a new top-level field on `ProposerOptionPayload` (v2)**: the target gas limit signed into gloas proposer preferences, honored per-validator and in `default_config`. Resolution: per-validator `gas_limit` → `default_config.gas_limit` → chain default (zero values fall through). - **Two independent gas limit signals**: relay registrations (mev-boost, pre-gloas) always read `builder.gas_limit`; proposer preferences always read the top-level `gas_limit`. A v2 file migrated before gloas can keep its `builder` block (`enabled`, `relays`, `gas_limit`) for registrations while declaring the preferences target at the top level — the two never mix. - **Keymanager gas-limit API is version-aware**: on v1 settings it keeps managing the builder gas limit (registration signal); on v2 it manages the top-level gas limit (preferences signal). ~~ethereum/keymanager-APIs#86 updated here~~ people did not favor this Beacon-node changes are not in this PR. ### Example files v1 (legacy — unchanged, still supported): ```json { "proposer_config": { "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": { "fee_recipient": "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3", "builder": { "enabled": true, "gas_limit": "45000000", "relays": ["https://example-relay.com"] } } }, "default_config": { "fee_recipient": "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", "builder": { "enabled": true, "gas_limit": "40000000" } } } ``` v2 (new format — the `builder` block is optional and keeps its registration meaning until gloas): ```json { "version": 2, "proposer_config": { "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": { "fee_recipient": "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3", "gas_limit": "45000000", "builder": { "enabled": true, "gas_limit": "45000000", "relays": ["https://example-relay.com"] } } }, "default_config": { "fee_recipient": "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", "gas_limit": "40000000" } } ``` ### Behavior matrix "Registrations" = signed validator registrations to builder relays (pre-gloas only). "Preferences" = signed gloas proposer preferences (submission begins one epoch before the fork for next-epoch duties). | Schema | Builder block | Before gloas | At/after gloas | |---|---|---|---| | v1 | with builder (enabled) | Registrations sent with `builder.gas_limit`. Preferences (from gloas−1) use the chain default — the builder gas limit isn't consulted for preferences until the migration promotes it. Startup deprecation warning (when gloas is scheduled). | Auto-migrated to v2 on the first settings push: `builder.gas_limit` promoted to top-level `gas_limit` (unless already set), builder blocks dropped, persisted to DB. Registrations stop; preferences carry the promoted gas limit. | | v1 | without builder | No registrations. Preferences use the chain default. | Auto-migrated to v2 (version bump only). Preferences use the chain default; fee recipients unchanged. | | v2 | with builder (enabled) | Registrations sent with `builder.gas_limit`, exactly as on v1. Preferences use the top-level `gas_limit`. | Registrations stop at the fork; builder block becomes inert. Preferences use the top-level `gas_limit`. | | v2 | without builder | No registrations — explicit opt-out. Preferences (from gloas−1) use the top-level `gas_limit`. | Steady state: preferences only. | **test kurtosis** check that proposer preferences are updated correctly, snooper is enabled. ``` extra_files: proposer-config-v2.json: | { "version": 2, "default_config": { "fee_recipient": "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", "gas_limit": "40000000" } } participants_matrix: el: - el_type: ethrex el_image: ethpandaops/ethrex:glamsterdam-devnet-4 cl: - cl_type: prysm cl_image: gcr.io/offchainlabs/prysm/beacon-chain:latest vc_image: gcr.io/offchainlabs/prysm/validator:latest cl_extra_params: - --verbosity=debug vc_extra_mounts: /proposer: proposer-config-v2.json vc_extra_params: - --enable-beacon-rest-api - --verbosity=debug # Point prysm vc at the mounted v2 proposer settings file. - --proposer-settings-file=/proposer/proposer-config-v2.json count: 2 network_params: fulu_fork_epoch: 0 gloas_fork_epoch: 2 seconds_per_slot: 6 genesis_delay: 40 additional_services: - dora - spamoor # spamoor generates load so payloads are non-empty: eoatx for normal EL txs, blobs to exercise the # blob/KZG path through the stateless envelope Contents flow. spamoor_params: spammers: - scenario: eoatx config: throughput: 10 - scenario: blobs config: throughput: 2 keymanager_enabled: true global_log_level: debug ``` ``` curl -s -H "Authorization: Bearer $TOKEN" $URL/eth/v1/keystores | jq -r '.data[].validating_pubkey' PK=<pubkey> curl -s -H "Authorization: Bearer $TOKEN" $URL/eth/v1/validator/$PK/gas_limit curl -s -X DELETE -H "Authorization: Bearer $TOKEN" $URL/eth/v1/validator/$PK/gas_limit ``` **Note** beacon node updates are not part of this pr. **Which issues(s) does this PR fix?** Fixes # **Other notes for review** **Acknowledgements** - [x] I have read [CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md). - [x] I have included a uniquely named [changelog fragment file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd). - [x] I have added a description with sufficient context for reviewers to understand this PR. - [x] I have tested that my changes work as expected and I added a testing plan to the PR description (if applicable). --------- Co-authored-by: Jun Song <87601811+syjn99@users.noreply.github.com>
1 parent ddb4132 commit ff8ee84

15 files changed

Lines changed: 1578 additions & 184 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
### Changed
2+
3+
- proposer settings no longer recognize the builder option post gloas and introduces a new gas_limit option for proposer preferences, supported per validator and in the default config.
4+
- the builder gas_limit and the new top-level gas_limit are independent signals: relay registrations keep reading builder.gas_limit pre-gloas, while proposer preferences read only the top-level gas_limit (falling back to the default config, then the chain default).
5+
- gas limit keymanager endpoint continues to update gas limit on a per validator basis post gloas.
6+
- v1 proposer settings files remain supported without modification: a deprecation warning is logged on gloas-scheduled networks and settings are upgraded automatically in the validator client at the gloas fork, promoting builder gas limits to the top level unless one is already set. Settings already on v2 are never rewritten.

config/proposer/BUILD.bazel

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "go_default_library",
5-
srcs = ["settings.go"],
5+
srcs = [
6+
"log.go",
7+
"settings.go",
8+
],
69
importpath = "github.com/OffchainLabs/prysm/v7/config/proposer",
710
visibility = ["//visibility:public"],
811
deps = [
912
"//config:go_default_library",
1013
"//config/fieldparams:go_default_library",
14+
"//config/params:go_default_library",
1115
"//consensus-types/validator:go_default_library",
1216
"//encoding/bytesutil:go_default_library",
1317
"//proto/prysm/v1alpha1/validator-client:go_default_library",
1418
"@com_github_ethereum_go_ethereum//common:go_default_library",
1519
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
1620
"@com_github_pkg_errors//:go_default_library",
21+
"@com_github_sirupsen_logrus//:go_default_library",
1722
],
1823
)
1924

@@ -26,8 +31,10 @@ go_test(
2631
"//config/params:go_default_library",
2732
"//consensus-types/validator:go_default_library",
2833
"//encoding/bytesutil:go_default_library",
34+
"//testing/assert:go_default_library",
2935
"//testing/require:go_default_library",
3036
"@com_github_ethereum_go_ethereum//common:go_default_library",
3137
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
38+
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
3239
],
3340
)

config/proposer/loader/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_test(
1313
"//config/proposer:go_default_library",
1414
"//consensus-types/validator:go_default_library",
1515
"//encoding/bytesutil:go_default_library",
16+
"//proto/prysm/v1alpha1/validator-client:go_default_library",
1617
"//testing/assert:go_default_library",
1718
"//testing/require:go_default_library",
1819
"//validator/db/iface:go_default_library",

config/proposer/loader/loader.go

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,25 @@ func (psl *SettingsLoader) Load(cliCtx *cli.Context) (*proposer.Settings, error)
179179
if err != nil {
180180
return nil, err
181181
}
182+
ps.WarnDeprecatedSchema()
182183
if err := psl.db.SaveProposerSettings(cliCtx.Context, ps); err != nil {
183184
return nil, err
184185
}
185186
return ps, nil
186187
}
187188

189+
func hasBuilderShape(p *validatorpb.ProposerSettingsPayload) bool {
190+
if p.DefaultConfig != nil && p.DefaultConfig.Builder != nil {
191+
return true
192+
}
193+
for _, o := range p.ProposerConfig {
194+
if o != nil && o.Builder != nil {
195+
return true
196+
}
197+
}
198+
return false
199+
}
200+
188201
func (psl *SettingsLoader) applyOverrides() {
189202
if psl.options.builderConfig != nil && psl.options.gasLimit != nil {
190203
psl.options.builderConfig.GasLimit = *psl.options.gasLimit
@@ -250,41 +263,51 @@ func (psl *SettingsLoader) processProposerSettings(loadedSettings, dbSettings *v
250263
return newSettings
251264
}
252265

253-
// mergeProposerSettings merges database settings with loaded settings, giving precedence to loadedSettings
266+
// mergeProposerSettings merges database settings with loaded settings, giving
267+
// precedence to loadedSettings. Dispatches by schema version: v1 still flows
268+
// through Builder; v2 lives on Option directly.
254269
func mergeProposerSettings(loaded, db *validatorpb.ProposerSettingsPayload, options *flagOptions) *validatorpb.ProposerSettingsPayload {
255270
merged := &validatorpb.ProposerSettingsPayload{}
271+
if db != nil {
272+
merged.Version = db.Version
273+
}
274+
// v1-shaped source content must not inherit DB's v2, else the runtime upgrade is skipped.
275+
if loaded != nil && (loaded.Version != 0 || hasBuilderShape(loaded)) {
276+
merged.Version = loaded.Version
277+
}
256278

257-
// Apply builder config overrides
258279
var builderConfig *validatorpb.BuilderConfig
259280
var gasLimitOnly *validator.Uint64
260-
261281
if options != nil {
262282
if options.builderConfig != nil {
263283
builderConfig = options.builderConfig.ToConsensus()
264284
}
265-
if options.gasLimit != nil {
266-
gasLimitOnly = options.gasLimit
267-
}
285+
gasLimitOnly = options.gasLimit
268286
}
269287

270-
// Merge DefaultConfig
288+
if merged.Version == proposer.SchemaV2 {
289+
return mergeProposerSettingsV2(merged, loaded, db, gasLimitOnly)
290+
}
291+
return mergeProposerSettingsV1(merged, loaded, db, builderConfig, gasLimitOnly)
292+
}
293+
294+
func mergeProposerSettingsV1(merged, loaded, db *validatorpb.ProposerSettingsPayload, builderConfig *validatorpb.BuilderConfig, gasLimitOnly *validator.Uint64) *validatorpb.ProposerSettingsPayload {
295+
stripDBBuilder := builderConfig == nil
296+
271297
if db != nil && db.DefaultConfig != nil {
272298
merged.DefaultConfig = db.DefaultConfig
273-
// db always falls back to local building if no builder settings are provided
274-
if builderConfig == nil {
299+
if stripDBBuilder {
275300
db.DefaultConfig.Builder = nil
276301
}
277302
}
278303
if loaded != nil && loaded.DefaultConfig != nil {
279304
merged.DefaultConfig = loaded.DefaultConfig
280305
}
281306

282-
// Merge ProposerConfig
283307
if db != nil && len(db.ProposerConfig) > 0 {
284308
merged.ProposerConfig = db.ProposerConfig
285-
for _, option := range db.ProposerConfig {
286-
// db always falls back to local building if no builder settings are provided
287-
if builderConfig == nil {
309+
if stripDBBuilder {
310+
for _, option := range db.ProposerConfig {
288311
option.Builder = nil
289312
}
290313
}
@@ -302,10 +325,46 @@ func mergeProposerSettings(loaded, db *validatorpb.ProposerSettingsPayload, opti
302325
}
303326
}
304327

305-
if merged.DefaultConfig == nil && builderConfig != nil {
306-
merged.DefaultConfig = &validatorpb.ProposerOptionPayload{Builder: builderConfig}
328+
if merged.DefaultConfig == nil {
329+
switch {
330+
case builderConfig != nil:
331+
merged.DefaultConfig = &validatorpb.ProposerOptionPayload{Builder: builderConfig}
332+
case gasLimitOnly != nil:
333+
merged.DefaultConfig = &validatorpb.ProposerOptionPayload{
334+
Builder: &validatorpb.BuilderConfig{Enabled: false, GasLimit: *gasLimitOnly},
335+
}
336+
}
337+
}
338+
return merged
339+
}
340+
341+
func mergeProposerSettingsV2(merged, loaded, db *validatorpb.ProposerSettingsPayload, gasLimitOnly *validator.Uint64) *validatorpb.ProposerSettingsPayload {
342+
if db != nil && db.DefaultConfig != nil {
343+
merged.DefaultConfig = db.DefaultConfig
344+
}
345+
if loaded != nil && loaded.DefaultConfig != nil {
346+
merged.DefaultConfig = loaded.DefaultConfig
347+
}
348+
if db != nil && len(db.ProposerConfig) > 0 {
349+
merged.ProposerConfig = db.ProposerConfig
350+
}
351+
if loaded != nil && len(loaded.ProposerConfig) > 0 {
352+
merged.ProposerConfig = loaded.ProposerConfig
307353
}
308354

355+
if gasLimitOnly == nil {
356+
return merged
357+
}
358+
if merged.DefaultConfig == nil {
359+
merged.DefaultConfig = &validatorpb.ProposerOptionPayload{GasLimit: *gasLimitOnly}
360+
} else {
361+
merged.DefaultConfig.GasLimit = *gasLimitOnly
362+
}
363+
for _, option := range merged.ProposerConfig {
364+
if option != nil {
365+
option.GasLimit = *gasLimitOnly
366+
}
367+
}
309368
return merged
310369
}
311370

@@ -320,7 +379,13 @@ func processBuilderConfig(current *validatorpb.BuilderConfig, override *validato
320379
}
321380
return current
322381
}
323-
return override
382+
if override != nil {
383+
return override
384+
}
385+
if gasLimitOnly != nil {
386+
return &validatorpb.BuilderConfig{Enabled: false, GasLimit: *gasLimitOnly}
387+
}
388+
return nil
324389
}
325390

326391
func reviewGasLimit(gasLimit validator.Uint64) validator.Uint64 {

0 commit comments

Comments
 (0)