@chainsafe/lodestar 1.47.0-dev.f591cb177e → 1.48.0-dev.67344272c0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.git-data.json +1 -1
  2. package/lib/cmds/beacon/handler.d.ts.map +1 -1
  3. package/lib/cmds/beacon/handler.js +10 -4
  4. package/lib/cmds/beacon/handler.js.map +1 -1
  5. package/lib/cmds/builder/loadKeypair.js +1 -1
  6. package/lib/cmds/builder/loadKeypair.js.map +1 -1
  7. package/lib/cmds/dev/options.d.ts +4 -0
  8. package/lib/cmds/dev/options.d.ts.map +1 -1
  9. package/lib/cmds/validator/blsToExecutionChange.js +1 -1
  10. package/lib/cmds/validator/blsToExecutionChange.js.map +1 -1
  11. package/lib/cmds/validator/handler.d.ts.map +1 -1
  12. package/lib/cmds/validator/handler.js +15 -2
  13. package/lib/cmds/validator/handler.js.map +1 -1
  14. package/lib/cmds/validator/keymanager/decryptKeystoreDefinitions.js +1 -1
  15. package/lib/cmds/validator/keymanager/decryptKeystoreDefinitions.js.map +1 -1
  16. package/lib/cmds/validator/keymanager/impl.d.ts +13 -2
  17. package/lib/cmds/validator/keymanager/impl.d.ts.map +1 -1
  18. package/lib/cmds/validator/keymanager/impl.js +41 -4
  19. package/lib/cmds/validator/keymanager/impl.js.map +1 -1
  20. package/lib/cmds/validator/keymanager/keystoreCache.js +1 -1
  21. package/lib/cmds/validator/keymanager/keystoreCache.js.map +1 -1
  22. package/lib/cmds/validator/keymanager/persistedKeys.d.ts.map +1 -1
  23. package/lib/cmds/validator/keymanager/persistedKeys.js +3 -1
  24. package/lib/cmds/validator/keymanager/persistedKeys.js.map +1 -1
  25. package/lib/cmds/validator/options.d.ts +4 -0
  26. package/lib/cmds/validator/options.d.ts.map +1 -1
  27. package/lib/cmds/validator/options.js +24 -0
  28. package/lib/cmds/validator/options.js.map +1 -1
  29. package/lib/cmds/validator/signers/index.js +1 -1
  30. package/lib/cmds/validator/signers/index.js.map +1 -1
  31. package/lib/cmds/validator/voluntaryExit.js +1 -1
  32. package/lib/cmds/validator/voluntaryExit.js.map +1 -1
  33. package/lib/networks/chiado.d.ts.map +1 -1
  34. package/lib/networks/chiado.js +4 -10
  35. package/lib/networks/chiado.js.map +1 -1
  36. package/lib/options/beaconNodeOptions/builder.d.ts.map +1 -1
  37. package/lib/options/beaconNodeOptions/builder.js +2 -2
  38. package/lib/options/beaconNodeOptions/builder.js.map +1 -1
  39. package/lib/options/beaconNodeOptions/chain.d.ts.map +1 -1
  40. package/lib/options/beaconNodeOptions/chain.js +1 -1
  41. package/lib/options/beaconNodeOptions/chain.js.map +1 -1
  42. package/lib/util/format.js +1 -1
  43. package/lib/util/format.js.map +1 -1
  44. package/lib/util/proposerConfig.d.ts +16 -11
  45. package/lib/util/proposerConfig.d.ts.map +1 -1
  46. package/lib/util/proposerConfig.js +128 -11
  47. package/lib/util/proposerConfig.js.map +1 -1
  48. package/package.json +21 -21
  49. package/src/cmds/beacon/handler.ts +10 -4
  50. package/src/cmds/builder/loadKeypair.ts +1 -1
  51. package/src/cmds/validator/blsToExecutionChange.ts +1 -1
  52. package/src/cmds/validator/handler.ts +29 -2
  53. package/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts +1 -1
  54. package/src/cmds/validator/keymanager/impl.ts +56 -4
  55. package/src/cmds/validator/keymanager/keystoreCache.ts +1 -1
  56. package/src/cmds/validator/keymanager/persistedKeys.ts +5 -1
  57. package/src/cmds/validator/options.ts +36 -0
  58. package/src/cmds/validator/signers/index.ts +1 -1
  59. package/src/cmds/validator/voluntaryExit.ts +1 -1
  60. package/src/networks/chiado.ts +4 -10
  61. package/src/options/beaconNodeOptions/builder.ts +4 -2
  62. package/src/options/beaconNodeOptions/chain.ts +2 -1
  63. package/src/util/format.ts +1 -1
  64. package/src/util/proposerConfig.ts +152 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainsafe/lodestar",
3
- "version": "1.47.0-dev.f591cb177e",
3
+ "version": "1.48.0-dev.67344272c0",
4
4
  "description": "Command line interface for lodestar",
5
5
  "author": "ChainSafe Systems",
6
6
  "license": "Apache-2.0",
@@ -59,26 +59,26 @@
59
59
  "dependencies": {
60
60
  "@chainsafe/bls-keygen": "^0.4.0",
61
61
  "@chainsafe/bls-keystore": "^3.1.0",
62
- "@chainsafe/blst": "^2.2.0",
63
62
  "@chainsafe/discv5": "^12.0.2",
64
63
  "@chainsafe/enr": "^6.0.2",
65
- "@chainsafe/persistent-merkle-tree": "^1.3.0",
66
- "@chainsafe/ssz": "^1.6.2",
64
+ "@chainsafe/lodestar-z": "catalog:",
65
+ "@chainsafe/persistent-merkle-tree": "^1.3.1",
66
+ "@chainsafe/ssz": "^1.6.3",
67
67
  "@chainsafe/threads": "^1.11.3",
68
- "@libp2p/crypto": "^5.1.13",
69
- "@libp2p/interface": "^3.1.0",
70
- "@libp2p/peer-id": "^6.0.4",
71
- "@lodestar/api": "^1.47.0-dev.f591cb177e",
72
- "@lodestar/beacon-node": "^1.47.0-dev.f591cb177e",
73
- "@lodestar/builder": "^1.47.0-dev.f591cb177e",
74
- "@lodestar/config": "^1.47.0-dev.f591cb177e",
75
- "@lodestar/db": "^1.47.0-dev.f591cb177e",
76
- "@lodestar/logger": "^1.47.0-dev.f591cb177e",
77
- "@lodestar/params": "^1.47.0-dev.f591cb177e",
78
- "@lodestar/state-transition": "^1.47.0-dev.f591cb177e",
79
- "@lodestar/types": "^1.47.0-dev.f591cb177e",
80
- "@lodestar/utils": "^1.47.0-dev.f591cb177e",
81
- "@lodestar/validator": "^1.47.0-dev.f591cb177e",
68
+ "@libp2p/crypto": "^5.1.23",
69
+ "@libp2p/interface": "^3.3.0",
70
+ "@libp2p/peer-id": "^6.0.15",
71
+ "@lodestar/api": "^1.48.0-dev.67344272c0",
72
+ "@lodestar/beacon-node": "^1.48.0-dev.67344272c0",
73
+ "@lodestar/builder": "^1.48.0-dev.67344272c0",
74
+ "@lodestar/config": "^1.48.0-dev.67344272c0",
75
+ "@lodestar/db": "^1.48.0-dev.67344272c0",
76
+ "@lodestar/logger": "^1.48.0-dev.67344272c0",
77
+ "@lodestar/params": "^1.48.0-dev.67344272c0",
78
+ "@lodestar/state-transition": "^1.48.0-dev.67344272c0",
79
+ "@lodestar/types": "^1.48.0-dev.67344272c0",
80
+ "@lodestar/utils": "^1.48.0-dev.67344272c0",
81
+ "@lodestar/validator": "^1.48.0-dev.67344272c0",
82
82
  "@multiformats/multiaddr": "^13.0.1",
83
83
  "deepmerge": "^4.3.1",
84
84
  "ethers": "^6.7.0",
@@ -92,9 +92,9 @@
92
92
  "yargs": "^17.7.1"
93
93
  },
94
94
  "devDependencies": {
95
- "@chainsafe/as-sha256": "^1.2.4",
95
+ "@chainsafe/as-sha256": "^1.2.5",
96
96
  "@ethereumjs/rlp": "^4.0.1",
97
- "@lodestar/test-utils": "^1.47.0-dev.f591cb177e",
97
+ "@lodestar/test-utils": "^1.48.0-dev.67344272c0",
98
98
  "@types/debug": "^4.1.7",
99
99
  "@types/inquirer": "^9.0.3",
100
100
  "@types/js-yaml": "^4.0.5",
@@ -108,5 +108,5 @@
108
108
  "web3": "^4.0.3",
109
109
  "web3-eth-accounts": "^4.0.3"
110
110
  },
111
- "gitHead": "07cdd0aad9cd2f4dcadda5e3a857656e9cd7855c"
111
+ "gitHead": "11114921165ea242fa2d33b68ac8813cdb75a4ee"
112
112
  }
@@ -1,13 +1,14 @@
1
1
  import path from "node:path";
2
2
  import {getHeapStatistics} from "node:v8";
3
3
  import {SignableENR} from "@chainsafe/enr";
4
+ import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
4
5
  import {hasher} from "@chainsafe/persistent-merkle-tree";
5
6
  import {BeaconDb, BeaconNode} from "@lodestar/beacon-node";
6
7
  import {ChainForkConfig, createBeaconConfig} from "@lodestar/config";
7
8
  import {LevelDbController} from "@lodestar/db/controller/level";
8
9
  import {LoggerNode, getNodeLogger} from "@lodestar/logger/node";
9
- import {ACTIVE_PRESET, PresetName} from "@lodestar/params";
10
- import {createBeaconStateView, createPubkeyCache, syncPubkeys} from "@lodestar/state-transition";
10
+ import {ACTIVE_PRESET, MAX_PENDING_DEPOSITS_PER_EPOCH, PresetName, SLOTS_PER_EPOCH} from "@lodestar/params";
11
+ import {createBeaconStateView} from "@lodestar/state-transition";
11
12
  import {ErrorAborted, bytesToInt, formatBytes} from "@lodestar/utils";
12
13
  import {ProcessShutdownCallback} from "@lodestar/validator";
13
14
  import {BeaconNodeOptions, getBeaconConfigFromArgs} from "../../config/index.js";
@@ -78,8 +79,13 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise<void
78
79
  wsCheckpoint,
79
80
  } = await initBeaconState(args, beaconPaths.dataDir, config, db, logger);
80
81
  const beaconConfig = createBeaconConfig(config, anchorState.genesisValidatorsRoot);
81
- const pubkeyCache = createPubkeyCache();
82
- syncPubkeys(pubkeyCache, anchorState.validators.getAllReadonlyValues());
82
+ // Reserve 3 months of worst-case registry growth (MAX_PENDING_DEPOSITS_PER_EPOCH per epoch),
83
+ // over a year at organic rates, to avoid routine cache reallocations. Cache growth is protected
84
+ // by its native lock; if this headroom is exceeded, it grows by the same fixed step.
85
+ const headroomEpochs = (90 * 24 * 60 * 60) / (config.SECONDS_PER_SLOT * SLOTS_PER_EPOCH);
86
+ const pubkeyCacheHeadroom = MAX_PENDING_DEPOSITS_PER_EPOCH * Math.ceil(headroomEpochs);
87
+ pubkeyCache.ensureCapacity(anchorState.validators.length + pubkeyCacheHeadroom);
88
+ pubkeyCache.syncPubkeys(anchorState.validators.getAllReadonlyValues());
83
89
  const anchorStateView = args["chain.nativeStateView"]
84
90
  ? createBeaconStateView({useNative: true, stateBytes: anchorStateBytes})
85
91
  : createBeaconStateView({useNative: false, anchorState, config: beaconConfig, pubkeyCache});
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import {Keystore} from "@chainsafe/bls-keystore";
3
- import {SecretKey} from "@chainsafe/blst";
3
+ import {SecretKey} from "@chainsafe/lodestar-z/blst";
4
4
  import {Keypair} from "@lodestar/builder";
5
5
  import {Logger} from "@lodestar/utils";
6
6
  import {ensure0xPrefix} from "../../util/format.js";
@@ -1,4 +1,4 @@
1
- import {SecretKey} from "@chainsafe/blst";
1
+ import {SecretKey} from "@chainsafe/lodestar-z/blst";
2
2
  import {getClient} from "@lodestar/api";
3
3
  import {createBeaconConfig} from "@lodestar/config";
4
4
  import {DOMAIN_BLS_TO_EXECUTION_CHANGE, ForkName} from "@lodestar/params";
@@ -28,7 +28,13 @@ import {
28
28
  parseLoggerArgs,
29
29
  parseProposerConfig,
30
30
  } from "../../util/index.js";
31
- import {parseBuilderBoostFactor, parseBuilderSelection} from "../../util/proposerConfig.js";
31
+ import {
32
+ parseBuilderBoostFactor,
33
+ parseBuilderGweiAmount,
34
+ parseBuilderMinBid,
35
+ parseBuilderSelection,
36
+ parseBuilderUrls,
37
+ } from "../../util/proposerConfig.js";
32
38
  import {getVersionData} from "../../util/version.js";
33
39
  import {KeymanagerApi} from "./keymanager/impl.js";
34
40
  import {IPersistedKeysBackend} from "./keymanager/interface.js";
@@ -212,7 +218,8 @@ export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Pr
212
218
  validator,
213
219
  persistedKeysBackend,
214
220
  abortController.signal,
215
- proposerConfigWriteDisabled
221
+ proposerConfigWriteDisabled,
222
+ args.allowDangerousTrustedPayments
216
223
  );
217
224
  const keymanagerServer = new KeymanagerRestApiServer(
218
225
  {
@@ -250,6 +257,9 @@ function getProposerConfigFromArgs(
250
257
  args["builder.selection"] ?? (args.builder ? defaultOptions.builderAliasSelection : undefined)
251
258
  ),
252
259
  boostFactor: parseBuilderBoostFactor(args["builder.boostFactor"]),
260
+ minBid: parseBuilderMinBid(args["builder.minBid"]),
261
+ maxExecutionPayment: parseBuilderGweiAmount(args["builder.maxExecutionPayment"]),
262
+ builders: parseBuilderUrls(args["builder.urls"]),
253
263
  },
254
264
  };
255
265
 
@@ -273,6 +283,23 @@ function getProposerConfigFromArgs(
273
283
  valProposerConfig = {defaultConfig} as ValidatorProposerConfig;
274
284
  }
275
285
  }
286
+
287
+ // Trusted execution payments are only backed by the builder's promise to pay, require an
288
+ // explicit opt-in before any configuration source can set a max execution payment above 0
289
+ if (args.allowDangerousTrustedPayments !== true) {
290
+ const configs = [valProposerConfig.defaultConfig, ...Object.values(valProposerConfig.proposerConfig ?? {})];
291
+ const hasTrustedPayment = configs.some(
292
+ (config) =>
293
+ (config.builder?.maxExecutionPayment ?? 0n) > 0n ||
294
+ (config.builder?.builders ?? []).some((entry) => (entry.maxExecutionPayment ?? 0n) > 0n)
295
+ );
296
+ if (hasTrustedPayment) {
297
+ throw new YargsError(
298
+ "Configuring a builder max execution payment above 0 requires --allowDangerousTrustedPayments"
299
+ );
300
+ }
301
+ }
302
+
276
303
  return valProposerConfig;
277
304
  }
278
305
 
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import {Keystore} from "@chainsafe/bls-keystore";
4
- import {SecretKey} from "@chainsafe/blst";
4
+ import {SecretKey} from "@chainsafe/lodestar-z/blst";
5
5
  import {LogLevel, Logger} from "@lodestar/utils";
6
6
  import {SignerLocal, SignerType} from "@lodestar/validator";
7
7
  import {lockFilepath, unlockFilepath} from "../../../util/lockfile.js";
@@ -1,7 +1,8 @@
1
1
  import {Keystore} from "@chainsafe/bls-keystore";
2
- import {SecretKey} from "@chainsafe/blst";
2
+ import {SecretKey} from "@chainsafe/lodestar-z/blst";
3
3
  import {
4
4
  BuilderBoostFactorData,
5
+ BuilderConfigData,
5
6
  DeleteRemoteKeyStatus,
6
7
  DeletionStatus,
7
8
  FeeRecipientData,
@@ -32,12 +33,13 @@ export class KeymanagerApi implements Api {
32
33
  private readonly validator: Validator,
33
34
  private readonly persistedKeysBackend: IPersistedKeysBackend,
34
35
  private readonly signal: AbortSignal,
35
- private readonly proposerConfigWriteDisabled?: boolean
36
+ private readonly proposerConfigWriteDisabled?: boolean,
37
+ private readonly allowDangerousTrustedPayments?: boolean
36
38
  ) {}
37
39
 
38
40
  private checkIfProposerWriteEnabled(): void {
39
41
  if (this.proposerConfigWriteDisabled === true) {
40
- throw Error("proposerSettingsFile option activated");
42
+ throw new ApiError(403, "proposerSettingsFile option activated");
41
43
  }
42
44
  }
43
45
 
@@ -379,6 +381,48 @@ export class KeymanagerApi implements Api {
379
381
  return {status: 204};
380
382
  }
381
383
 
384
+ async getBuilderConfig({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["getBuilderConfig"]> {
385
+ this.assertValidKnownPubkey(pubkey);
386
+ return {data: this.validator.validatorStore.getBuilderConfig(pubkey)};
387
+ }
388
+
389
+ async setBuilderConfig({
390
+ pubkey,
391
+ builderConfig,
392
+ }: {
393
+ pubkey: PubkeyHex;
394
+ builderConfig: BuilderConfigData;
395
+ }): ReturnType<Api["setBuilderConfig"]> {
396
+ this.checkIfProposerWriteEnabled();
397
+ this.assertValidKnownPubkey(pubkey);
398
+
399
+ if (
400
+ this.allowDangerousTrustedPayments !== true &&
401
+ builderConfig.builders?.some((entry) => (entry.maxExecutionPayment ?? 0n) > 0n)
402
+ ) {
403
+ throw new ApiError(
404
+ 400,
405
+ "Configuring a builder max execution payment above 0 requires --allowDangerousTrustedPayments"
406
+ );
407
+ }
408
+
409
+ try {
410
+ this.validator.validatorStore.setBuilderConfig(pubkey, builderConfig);
411
+ } catch (e) {
412
+ throw new ApiError(400, (e as Error).message);
413
+ }
414
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
415
+ return {status: 202};
416
+ }
417
+
418
+ async deleteBuilderConfig({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["deleteBuilderConfig"]> {
419
+ this.checkIfProposerWriteEnabled();
420
+ this.assertValidKnownPubkey(pubkey);
421
+ this.validator.validatorStore.deleteBuilderConfig(pubkey);
422
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
423
+ return {status: 204};
424
+ }
425
+
382
426
  async getProposerConfig({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["getProposerConfig"]> {
383
427
  this.assertValidKnownPubkey(pubkey);
384
428
 
@@ -390,7 +434,15 @@ export class KeymanagerApi implements Api {
390
434
  ? {
391
435
  ...config.builder,
392
436
  // Default JSON serialization can't handle BigInt
393
- boostFactor: config.builder.boostFactor ? config.builder.boostFactor.toString() : undefined,
437
+ boostFactor: config.builder.boostFactor?.toString(),
438
+ minBid: config.builder.minBid?.toString(),
439
+ maxExecutionPayment: config.builder.maxExecutionPayment?.toString(),
440
+ builders: config.builder.builders?.map((entry) => ({
441
+ ...entry,
442
+ maxExecutionPayment: entry.maxExecutionPayment?.toString(),
443
+ minBid: entry.minBid?.toString(),
444
+ builderBoostFactor: entry.builderBoostFactor?.toString(),
445
+ })),
394
446
  }
395
447
  : undefined,
396
448
  };
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import {Keystore} from "@chainsafe/bls-keystore";
4
- import {SecretKey} from "@chainsafe/blst";
4
+ import {SecretKey} from "@chainsafe/lodestar-z/blst";
5
5
  import {fromHex, toHex, toPubkeyHex} from "@lodestar/utils";
6
6
  import {SignerLocal, SignerType} from "@lodestar/validator";
7
7
  import {writeFile600Perm} from "../../../util/file.js";
@@ -53,7 +53,11 @@ export class PersistedKeysBackend implements IPersistedKeysBackend {
53
53
  if (proposerConfig !== null) {
54
54
  // if proposerConfig is not empty write or update the json to file
55
55
  const {proposerDirPath} = this.getValidatorPaths(pubkeyHex);
56
- writeFile600Perm(proposerDirPath, JSON.stringify(proposerConfig));
56
+ writeFile600Perm(
57
+ proposerDirPath,
58
+ // Default JSON serialization can't handle BigInt
59
+ JSON.stringify(proposerConfig, (_key, value) => (typeof value === "bigint" ? value.toString() : value))
60
+ );
57
61
  } else {
58
62
  this.deleteProposerConfig(pubkeyHex);
59
63
  }
@@ -48,6 +48,10 @@ export type IValidatorCliArgs = AccountValidatorArgs &
48
48
  builder?: boolean;
49
49
  "builder.selection"?: string;
50
50
  "builder.boostFactor"?: string;
51
+ "builder.minBid"?: string;
52
+ "builder.urls"?: string[];
53
+ "builder.maxExecutionPayment"?: string;
54
+ allowDangerousTrustedPayments?: boolean;
51
55
 
52
56
  /** @deprecated */
53
57
  useProduceBlockV3?: boolean;
@@ -276,6 +280,38 @@ export const validatorOptions: CliCommandOptions<IValidatorCliArgs> = {
276
280
  group: "builder",
277
281
  },
278
282
 
283
+ "builder.minBid": {
284
+ type: "string",
285
+ description:
286
+ "Minimum counted total payment in Gwei accepted from a builder bid. The total is the bid value plus its execution payment up to the configured cap. Only used post-Gloas",
287
+ defaultDescription: `${defaultOptions.builderMinBid}`,
288
+ group: "builder",
289
+ },
290
+
291
+ "builder.urls": {
292
+ description:
293
+ "URL(s) of external builders to request execution payload bids from. Auth data agreed with a builder may be appended as a hex fragment, e.g. https://builder.example.com#0x0123, otherwise the UTF-8 bytes of the URL are used. Only used post-Gloas",
294
+ type: "array",
295
+ string: true,
296
+ coerce: (urls: string[]): string[] => urls.flatMap((url) => url.split(",")),
297
+ group: "builder",
298
+ },
299
+
300
+ "builder.maxExecutionPayment": {
301
+ type: "string",
302
+ description:
303
+ "Maximum execution layer payment in Gwei counted toward a builder bid. A value of 0 means only trustless payments via the builder's staked collateral count toward the bid. Values above 0 require `--allowDangerousTrustedPayments`. Only used post-Gloas",
304
+ defaultDescription: `${defaultOptions.builderMaxExecutionPayment}`,
305
+ group: "builder",
306
+ },
307
+
308
+ allowDangerousTrustedPayments: {
309
+ type: "boolean",
310
+ description:
311
+ "Allow configuring a builder max execution payment above 0. Trusted execution layer payments are only backed by the builder's promise to pay, not by its staked collateral, a builder that fails to pay cannot be penalized in protocol",
312
+ group: "builder",
313
+ },
314
+
279
315
  useProduceBlockV3: {
280
316
  hidden: true,
281
317
  deprecated: true,
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import {deriveEth2ValidatorKeys, deriveKeyFromMnemonic} from "@chainsafe/bls-keygen";
3
- import {SecretKey} from "@chainsafe/blst";
3
+ import {SecretKey} from "@chainsafe/lodestar-z/blst";
4
4
  import {interopSecretKey} from "@lodestar/state-transition";
5
5
  import {LogLevel, Logger, isValidHttpUrl} from "@lodestar/utils";
6
6
  import {Signer, SignerType, externalSignerGetKeys} from "@lodestar/validator";
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import inquirer from "inquirer";
4
- import {Signature} from "@chainsafe/blst";
4
+ import {Signature} from "@chainsafe/lodestar-z/blst";
5
5
  import {ApiClient, getClient} from "@lodestar/api";
6
6
  import {BeaconConfig, createBeaconConfig} from "@lodestar/config";
7
7
  import {
@@ -5,14 +5,8 @@ export const genesisStateRoot = "0xa48419160f8f146ecaa53d12a5d6e1e6af414a328afdc
5
5
  export const bootnodesFileUrl = "https://raw.githubusercontent.com/gnosischain/configs/main/chiado/bootnodes.yaml";
6
6
 
7
7
  export const bootEnrs = [
8
- "enr:-L64QOijsdi9aVIawMb5h5PWueaPM9Ai6P17GNPFlHzz7MGJQ8tFMdYrEx8WQitNKLG924g2Q9cCdzg54M0UtKa3QIKCMxaHYXR0bmV0c4j__________4RldGgykDE2cEMCAABv__________-CaWSCdjSCaXCEi5AaWYlzZWNwMjU2azGhA8CjTkD4m1s8FbKCN18LgqlYcE65jrT148vFtwd9U62SiHN5bmNuZXRzD4N0Y3CCIyiDdWRwgiMo",
9
- "enr:-L64QKYKGQj5ybkfBxyFU5IEVzP7oJkGHJlie4W8BCGAYEi4P0mmMksaasiYF789mVW_AxYVNVFUjg9CyzmdvpyWQ1KCMlmHYXR0bmV0c4j__________4RldGgykDE2cEMCAABv__________-CaWSCdjSCaXCEi5CtNolzZWNwMjU2azGhAuA7BAwIijy1z81AO9nz_MOukA1ER68rGA67PYQ5pF1qiHN5bmNuZXRzD4N0Y3CCIyiDdWRwgiMo",
10
- "enr:-Ly4QJJUnV9BxP_rw2Bv7E9iyw4sYS2b4OQZIf4Mu_cA6FljJvOeSTQiCUpbZhZjR4R0VseBhdTzrLrlHrAuu_OeZqgJh2F0dG5ldHOI__________-EZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhIuQGnOJc2VjcDI1NmsxoQPT_u3IjDtB2r-nveH5DhUmlM8F2IgLyxhmwmqW4L5k3ohzeW5jbmV0cw-DdGNwgiMog3VkcIIjKA",
11
- "enr:-MK4QCkOyqOTPX1_-F-5XVFjPclDUc0fj3EeR8FJ5-hZjv6ARuGlFspM0DtioHn1r6YPUXkOg2g3x6EbeeKdsrvVBYmGAYQKrixeh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhIuQGlWJc2VjcDI1NmsxoQKdW3-DgLExBkpLGMRtuM88wW_gZkC7Yeg0stYDTrlynYhzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA",
12
- "enr:-Ly4QLYLNqrjvSxD3lpAPBUNlxa6cIbe79JqLZLFcZZjWoCjZcw-85agLUErHiygG2weRSCLnd5V460qTbLbwJQsfZkoh2F0dG5ldHOI__________-EZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhKq7mu-Jc2VjcDI1NmsxoQP900YAYa9kdvzlSKGjVo-F3XVzATjOYp3BsjLjSophO4hzeW5jbmV0cw-DdGNwgiMog3VkcIIjKA",
13
- "enr:-Ly4QCGeYvTCNOGKi0mKRUd45rLj96b4pH98qG7B9TCUGXGpHZALtaL2-XfjASQyhbCqENccI4PGXVqYTIehNT9KJMQgh2F0dG5ldHOI__________-EZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhIuQrVSJc2VjcDI1NmsxoQP9iDchx2PGl3JyJ29B9fhLCvVMN6n23pPAIIeFV-sHOIhzeW5jbmV0cw-DdGNwgiMog3VkcIIjKA",
14
- "enr:-Ly4QAtr21x5Ps7HYhdZkIBRBgcBkvlIfEel1YNjtFWf4cV3au2LgBGICz9PtEs9-p2HUl_eME8m1WImxTxSB3AkCMwBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhANHhOeJc2VjcDI1NmsxoQNLp1QPV8-pyMCohOtj6xGtSBM_GtVTqzlbvNsCF4ezkYhzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA",
15
- "enr:-Ly4QLgn8Bx6faigkKUGZQvd1HDToV2FAxZIiENK-lczruzQb90qJK-4E65ADly0s4__dQOW7IkLMW7ZAyJy2vtiLy8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhANFIw2Jc2VjcDI1NmsxoQMa-fWEy9UJHfOl_lix3wdY5qust78sHAqZnWwEiyqKgYhzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA",
16
- "enr:-KG4QF7z4LUdMfgwvh-fS-MDv_1hPSUCqGfyOWGLNJuoBHKFAMSHz8geQn8v3qDDbuSQKud3WIAjKqR4gqJoLBUEJ08ZhGV0aDKQDc1ElgAAAG___________4JpZIJ2NIJpcIQjzq5ciXNlY3AyNTZrMaECt7YO363pV54d3QdgnluL5kxzhCR_k0yM9C-G6bqMGoKDdGNwgiMog3VkcIIjKA",
17
- "enr:-LK4QCUTEmZrT1AgCKdyVgwnHL5J0VSoxsyjruAtwo-owBTBVEOyAnQRVNXlcW5aL-ycntk5oHDrKCR-DXZAlUAKpjEBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCdM7Z1BAAAb___________gmlkgnY0gmlwhCPSfheJc2VjcDI1NmsxoQNpdf8U9pzsU9m6Hzgd1rmTI-On-QImJnkZBGqDp4org4N0Y3CCIyiDdWRwgiMo",
8
+ "enr:-IS4QC9xcSNOKeUBrjOX1-qwcPDGOvRK-Aql_YnRfuYsuXElQawANMLTOz1YfrZTMI07B7DAl9hmk3ic5G7ZHQbq4HsUgmlkgnY0gmlwhDNL9gKJc2VjcDI1NmsxoQOzB8HdKe07eQBOxu5nexAWECV47zxlau23oEYqoToy9YN1ZHCCIyg",
9
+ "enr:-IS4QMpCWNqXmqbtXg7vQyYBwV2xTDpgTNpgAz060TY5cpJ3JdjuwAkLG01Vf9Vlfb0jtRhJEpj0CfteG-3K_V36bmECgmlkgnY0gmlwhDmBRt6Jc2VjcDI1NmsxoQNkoifSsk1ijP3-OR9pcL2VWCSfD5_syapyrXA83GcAvYN1ZHCCIyg",
10
+ "enr:-IS4QBa3hl_BcjrY4z_OvhRBx7qUMBFGkjkoedGNMXgC_fzLRBsg161SipeHWAkUnSuvBg_7529-Qfxcc7CAbokn4fMCgmlkgnY0gmlwhDmA_HuJc2VjcDI1NmsxoQL8xX-V_pyU_l3AIfVokHKMN1OMUG02vcy4AMOxdyi5jIN1ZHCCIyg",
11
+ "enr:-IS4QEuUku9zL0V3hFHt41fqmEdvphEvrOg0D4K_TgiiaePCDH2IPzt6B3O6NLHtgJNXk3UW68DL6KVBD4rjoW-nW9ACgmlkgnY0gmlwhDYnlGKJc2VjcDI1NmsxoQP3W66lisn2YOJF4B0QDEnURZ6tqzpi5gBy9rbMfpC70YN1ZHCCIyg",
18
12
  ];
@@ -31,7 +31,8 @@ export function parseArgs(args: ExecutionBuilderArgs): IBeaconNodeOptions["execu
31
31
 
32
32
  export const options: CliCommandOptions<ExecutionBuilderArgs> = {
33
33
  builder: {
34
- description: "Enable external builder",
34
+ description:
35
+ "Enable external builder. Pre-Gloas only, deprecated post-Gloas where builders are configured on the validator client",
35
36
  type: "boolean",
36
37
  default: defaultExecutionBuilderHttpOpts.enabled,
37
38
  group: "builder",
@@ -39,7 +40,8 @@ export const options: CliCommandOptions<ExecutionBuilderArgs> = {
39
40
 
40
41
  "builder.url": {
41
42
  alias: ["builder.urls"],
42
- description: "Url hosting the builder API",
43
+ description:
44
+ "Url hosting the builder API. Pre-Gloas only, deprecated post-Gloas where builders are configured on the validator client",
43
45
  defaultDescription: defaultExecutionBuilderHttpOpts.url,
44
46
  type: "string",
45
47
  group: "builder",
@@ -343,7 +343,8 @@ Will double processing times. Use only for debugging purposes.",
343
343
 
344
344
  "chain.maxCPStateEpochsOnDisk": {
345
345
  hidden: true,
346
- description: "Max epochs to cache checkpoint states on disk, used for PersistentCheckpointStateCache",
346
+ description:
347
+ "Max number of checkpoint state epochs to keep on disk. Default (Infinity) uses tiered pruning to bound disk usage during long non-finality; set a finite N to keep only the last N epochs instead (previous behavior)",
347
348
  type: "number",
348
349
  default: defaultOptions.chain.maxCPStateEpochsOnDisk,
349
350
  group: "chain",
@@ -1,4 +1,4 @@
1
- import {PublicKey} from "@chainsafe/blst";
1
+ import {PublicKey} from "@chainsafe/lodestar-z/blst";
2
2
  import {fromHex} from "@lodestar/utils";
3
3
 
4
4
  /**
@@ -1,10 +1,16 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import {routes} from "@lodestar/api";
4
+ import {BuilderEntryConfig, builderConfigDataFromJson} from "@lodestar/api/keymanager";
5
+ import {MAX_BUILDER_AUTH_DATA_SIZE, MAX_BUILDER_ENTRIES, MAX_BUILDER_URL_SIZE} from "@lodestar/params";
6
+ import {fromHex, isValidAsciiHttpUrl, toHex} from "@lodestar/utils";
4
7
  import {ValidatorProposerConfig} from "@lodestar/validator";
5
8
  import {parseFeeRecipient} from "./feeRecipient.js";
6
9
  import {readFile} from "./file.js";
7
10
 
11
+ const UINT64_MAX = 2n ** 64n - 1n;
12
+ const BUILDER_AUTH_DATA_PATTERN = new RegExp(`^0x(?:[a-fA-F0-9]{2}){1,${MAX_BUILDER_AUTH_DATA_SIZE}}$`);
13
+
8
14
  type ProposerConfig = ValidatorProposerConfig["defaultConfig"];
9
15
 
10
16
  type ProposerConfigFileSection = {
@@ -17,6 +23,9 @@ type ProposerConfigFileSection = {
17
23
  gas_limit?: number;
18
24
  selection?: routes.validator.BuilderSelection;
19
25
  boost_factor?: bigint;
26
+ min_bid?: bigint;
27
+ max_execution_payment?: bigint;
28
+ builders?: unknown;
20
29
  };
21
30
  };
22
31
 
@@ -54,7 +63,14 @@ function parseProposerConfigSection(
54
63
  overrideConfig?: ProposerConfig
55
64
  ): ProposerConfig {
56
65
  const {graffiti, strict_fee_recipient_check, fee_recipient, builder} = proposerFileSection;
57
- const {gas_limit, selection: builderSelection, boost_factor} = builder || {};
66
+ const {
67
+ gas_limit,
68
+ selection: builderSelection,
69
+ boost_factor,
70
+ min_bid,
71
+ max_execution_payment,
72
+ builders,
73
+ } = builder || {};
58
74
 
59
75
  if (graffiti !== undefined && typeof graffiti !== "string") {
60
76
  throw Error("graffiti is not 'string");
@@ -79,6 +95,29 @@ function parseProposerConfigSection(
79
95
  if (boost_factor !== undefined && typeof boost_factor !== "string") {
80
96
  throw Error("boost_factor is not 'string");
81
97
  }
98
+ if (min_bid !== undefined && typeof min_bid !== "string") {
99
+ throw Error("min_bid is not 'string");
100
+ }
101
+ if (max_execution_payment !== undefined && typeof max_execution_payment !== "string") {
102
+ throw Error("max_execution_payment is not 'string");
103
+ }
104
+
105
+ const parsedBuilder =
106
+ overrideConfig?.builder || builder
107
+ ? {
108
+ gasLimit: overrideConfig?.builder?.gasLimit ?? (gas_limit !== undefined ? Number(gas_limit) : undefined),
109
+ selection: overrideConfig?.builder?.selection ?? parseBuilderSelection(builderSelection),
110
+ boostFactor: overrideConfig?.builder?.boostFactor ?? parseBuilderBoostFactor(boost_factor),
111
+ minBid: overrideConfig?.builder?.minBid ?? parseBuilderMinBid(min_bid),
112
+ maxExecutionPayment:
113
+ overrideConfig?.builder?.maxExecutionPayment ?? parseBuilderGweiAmount(max_execution_payment),
114
+ builders: overrideConfig?.builder?.builders ?? parseBuilderEntries(builders),
115
+ }
116
+ : undefined;
117
+
118
+ if (overrideConfig?.builder?.builders !== undefined && builders !== undefined) {
119
+ throw Error("Cannot configure both --builder.urls and builders in the proposer settings file");
120
+ }
82
121
 
83
122
  return {
84
123
  graffiti: overrideConfig?.graffiti ?? graffiti,
@@ -86,21 +125,27 @@ function parseProposerConfigSection(
86
125
  overrideConfig?.strictFeeRecipientCheck ??
87
126
  (strict_fee_recipient_check ? stringtoBool(strict_fee_recipient_check) : undefined),
88
127
  feeRecipient: overrideConfig?.feeRecipient ?? (fee_recipient ? parseFeeRecipient(fee_recipient) : undefined),
89
- builder:
90
- overrideConfig?.builder || builder
91
- ? {
92
- gasLimit: overrideConfig?.builder?.gasLimit ?? (gas_limit !== undefined ? Number(gas_limit) : undefined),
93
- selection: overrideConfig?.builder?.selection ?? parseBuilderSelection(builderSelection),
94
- boostFactor: overrideConfig?.builder?.boostFactor ?? parseBuilderBoostFactor(boost_factor),
95
- }
96
- : undefined,
128
+ builder: parsedBuilder,
97
129
  };
98
130
  }
99
131
 
100
- export function readProposerConfigDir(filepath: string, filename: string): ProposerConfigFileSection {
132
+ export function readProposerConfigDir(filepath: string, filename: string): ProposerConfig {
101
133
  const proposerConfigStr = fs.readFileSync(path.join(filepath, filename), "utf8");
102
- const proposerConfigJSON = JSON.parse(proposerConfigStr) as ProposerConfigFileSection;
103
- return proposerConfigJSON;
134
+ // Persisted via `writeProposerConfig` with BigInt values serialized as strings
135
+ const persisted = JSON.parse(proposerConfigStr) as ProposerConfig;
136
+ if (persisted.builder) {
137
+ const {boostFactor, minBid, maxExecutionPayment, builders} = persisted.builder;
138
+ persisted.builder.boostFactor = boostFactor !== undefined ? BigInt(boostFactor) : undefined;
139
+ persisted.builder.minBid = minBid !== undefined ? BigInt(minBid) : undefined;
140
+ persisted.builder.maxExecutionPayment = maxExecutionPayment !== undefined ? BigInt(maxExecutionPayment) : undefined;
141
+ persisted.builder.builders = builders?.map((entry) => ({
142
+ ...entry,
143
+ maxExecutionPayment: entry.maxExecutionPayment !== undefined ? BigInt(entry.maxExecutionPayment) : undefined,
144
+ minBid: entry.minBid !== undefined ? BigInt(entry.minBid) : undefined,
145
+ builderBoostFactor: entry.builderBoostFactor !== undefined ? BigInt(entry.builderBoostFactor) : undefined,
146
+ }));
147
+ }
148
+ return persisted;
104
149
  }
105
150
 
106
151
  export function parseBuilderSelection(builderSelection?: string): routes.validator.BuilderSelection | undefined {
@@ -131,6 +176,100 @@ export function parseBuilderBoostFactor(boostFactor?: string): bigint | undefine
131
176
  if (!/^\d+$/.test(boostFactor)) {
132
177
  throw Error("Invalid input for builder boost factor, must be a valid number without decimals");
133
178
  }
179
+ const parsed = BigInt(boostFactor);
180
+ if (parsed > UINT64_MAX) {
181
+ throw Error("Invalid input for builder boost factor, must not exceed 2**64 - 1");
182
+ }
183
+
184
+ return parsed;
185
+ }
186
+
187
+ export function parseBuilderMinBid(minBid?: string | bigint): bigint | undefined {
188
+ if (minBid === undefined) return;
189
+
190
+ if (!/^\d+$/.test(minBid.toString())) {
191
+ throw Error("Invalid input for builder min bid, must be a valid number without decimals");
192
+ }
193
+ const parsed = BigInt(minBid);
194
+ if (parsed > UINT64_MAX) {
195
+ throw Error("Invalid input for builder min bid, must not exceed 2**64 - 1");
196
+ }
134
197
 
135
- return BigInt(boostFactor);
198
+ return parsed;
199
+ }
200
+
201
+ export function parseBuilderGweiAmount(amount?: string | bigint): bigint | undefined {
202
+ if (amount === undefined) return;
203
+
204
+ if (!/^\d+$/.test(amount.toString())) {
205
+ throw Error("Invalid input for builder Gwei amount, must be a valid number without decimals");
206
+ }
207
+ const parsed = BigInt(amount);
208
+ if (parsed > UINT64_MAX) {
209
+ throw Error("Invalid input for builder Gwei amount, must not exceed 2**64 - 1");
210
+ }
211
+
212
+ return parsed;
213
+ }
214
+
215
+ /**
216
+ * Parse per-builder entries from the proposer settings file, same shape and validation as the
217
+ * keymanager builders api. No two entries may share both their url and their auth data, an
218
+ * omitted auth data is compared as the value derived from the entry url.
219
+ */
220
+ export function parseBuilderEntries(builders?: unknown): BuilderEntryConfig[] | undefined {
221
+ if (builders === undefined) return undefined;
222
+
223
+ const {builders: entries} = builderConfigDataFromJson({builders});
224
+ const seenEntries = new Set<string>();
225
+ for (const entry of entries ?? []) {
226
+ if (!isValidAsciiHttpUrl(entry.url)) {
227
+ throw Error(`Invalid builder url: ${entry.url}`);
228
+ }
229
+ const authData = entry.authData !== undefined ? toHex(fromHex(entry.authData)) : toHex(Buffer.from(entry.url));
230
+ const entryKey = `${entry.url}|${authData}`;
231
+ if (seenEntries.has(entryKey)) {
232
+ throw Error(`Duplicate builder entry url=${entry.url}`);
233
+ }
234
+ seenEntries.add(entryKey);
235
+ }
236
+ return entries;
237
+ }
238
+
239
+ /**
240
+ * Parse builder urls into builder entries. Auth data agreed with a builder out of band may be
241
+ * appended as a hex fragment (`https://builder.example.com#0x0123`), it is stripped from the url
242
+ * and never sent on the wire. Without a fragment the auth data derives from the url.
243
+ */
244
+ export function parseBuilderUrls(urls?: string[]): BuilderEntryConfig[] | undefined {
245
+ if (urls === undefined) return undefined;
246
+
247
+ const entries: BuilderEntryConfig[] = [];
248
+ const seenEntries = new Set<string>();
249
+ for (const value of urls) {
250
+ const fragmentIndex = value.indexOf("#");
251
+ const url = fragmentIndex === -1 ? value : value.slice(0, fragmentIndex);
252
+ const authData = fragmentIndex === -1 ? undefined : value.slice(fragmentIndex + 1);
253
+ if (!isValidAsciiHttpUrl(url)) {
254
+ throw Error(`Invalid builder url: ${url}`);
255
+ }
256
+ if (Buffer.byteLength(url, "utf8") > MAX_BUILDER_URL_SIZE) {
257
+ throw Error(`Invalid builder url, must not exceed ${MAX_BUILDER_URL_SIZE} bytes: ${url}`);
258
+ }
259
+ if (authData !== undefined && !BUILDER_AUTH_DATA_PATTERN.test(authData)) {
260
+ throw Error(
261
+ `Invalid builder url auth data, must be a 0x-prefixed hex string of 1 to ${MAX_BUILDER_AUTH_DATA_SIZE} bytes: ${url}`
262
+ );
263
+ }
264
+ const entryKey = `${url}|${authData !== undefined ? toHex(fromHex(authData)) : toHex(Buffer.from(url))}`;
265
+ if (seenEntries.has(entryKey)) {
266
+ throw Error(`Duplicate builder url: ${url}`);
267
+ }
268
+ seenEntries.add(entryKey);
269
+ entries.push({url, authData});
270
+ }
271
+ if (entries.length > MAX_BUILDER_ENTRIES) {
272
+ throw Error(`Number of builder urls must not exceed ${MAX_BUILDER_ENTRIES}, got ${entries.length}`);
273
+ }
274
+ return entries;
136
275
  }