@lodestar/api 1.47.0-dev.d2f4b96574 → 1.47.0-dev.d79d81f994

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 (63) hide show
  1. package/lib/beacon/routes/beacon/block.d.ts +7 -0
  2. package/lib/beacon/routes/beacon/block.d.ts.map +1 -1
  3. package/lib/beacon/routes/beacon/block.js +7 -3
  4. package/lib/beacon/routes/beacon/block.js.map +1 -1
  5. package/lib/beacon/routes/beacon/pool.d.ts +2 -12
  6. package/lib/beacon/routes/beacon/pool.d.ts.map +1 -1
  7. package/lib/beacon/routes/beacon/pool.js +2 -3
  8. package/lib/beacon/routes/beacon/pool.js.map +1 -1
  9. package/lib/beacon/routes/debug.d.ts +3 -3
  10. package/lib/beacon/routes/debug.js +3 -3
  11. package/lib/beacon/routes/debug.js.map +1 -1
  12. package/lib/beacon/routes/lodestar.d.ts +33 -18
  13. package/lib/beacon/routes/lodestar.d.ts.map +1 -1
  14. package/lib/beacon/routes/lodestar.js +18 -2
  15. package/lib/beacon/routes/lodestar.js.map +1 -1
  16. package/lib/beacon/routes/validator.d.ts +98 -7
  17. package/lib/beacon/routes/validator.d.ts.map +1 -1
  18. package/lib/beacon/routes/validator.js +125 -23
  19. package/lib/beacon/routes/validator.js.map +1 -1
  20. package/lib/beacon/server/events.d.ts.map +1 -1
  21. package/lib/beacon/server/events.js +10 -2
  22. package/lib/beacon/server/events.js.map +1 -1
  23. package/lib/builder/routes.d.ts +48 -1
  24. package/lib/builder/routes.d.ts.map +1 -1
  25. package/lib/builder/routes.js +173 -1
  26. package/lib/builder/routes.js.map +1 -1
  27. package/lib/keymanager/index.d.ts +2 -2
  28. package/lib/keymanager/index.d.ts.map +1 -1
  29. package/lib/keymanager/index.js +1 -1
  30. package/lib/keymanager/index.js.map +1 -1
  31. package/lib/keymanager/routes.d.ts +63 -0
  32. package/lib/keymanager/routes.d.ts.map +1 -1
  33. package/lib/keymanager/routes.js +147 -0
  34. package/lib/keymanager/routes.js.map +1 -1
  35. package/lib/utils/client/httpClient.js +2 -2
  36. package/lib/utils/client/httpClient.js.map +1 -1
  37. package/lib/utils/client/request.d.ts +4 -1
  38. package/lib/utils/client/request.d.ts.map +1 -1
  39. package/lib/utils/client/request.js +11 -6
  40. package/lib/utils/client/request.js.map +1 -1
  41. package/lib/utils/client/response.js +1 -1
  42. package/lib/utils/client/response.js.map +1 -1
  43. package/lib/utils/metadata.d.ts +3 -0
  44. package/lib/utils/metadata.d.ts.map +1 -1
  45. package/lib/utils/metadata.js +4 -0
  46. package/lib/utils/metadata.js.map +1 -1
  47. package/lib/utils/types.d.ts +1 -1
  48. package/lib/utils/types.d.ts.map +1 -1
  49. package/package.json +8 -8
  50. package/src/beacon/routes/beacon/block.ts +18 -4
  51. package/src/beacon/routes/beacon/pool.ts +3 -11
  52. package/src/beacon/routes/debug.ts +3 -3
  53. package/src/beacon/routes/lodestar.ts +24 -19
  54. package/src/beacon/routes/validator.ts +189 -30
  55. package/src/beacon/server/events.ts +10 -3
  56. package/src/builder/routes.ts +227 -2
  57. package/src/keymanager/index.ts +9 -1
  58. package/src/keymanager/routes.ts +227 -0
  59. package/src/utils/client/httpClient.ts +2 -2
  60. package/src/utils/client/request.ts +12 -7
  61. package/src/utils/client/response.ts +1 -1
  62. package/src/utils/metadata.ts +5 -0
  63. package/src/utils/types.ts +1 -1
@@ -1,9 +1,12 @@
1
- import {ContainerType, Type, ValueOf} from "@chainsafe/ssz";
1
+ import {ByteListType, ContainerType, Type, ValueOf} from "@chainsafe/ssz";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {
4
4
  ForkPostDeneb,
5
5
  ForkPostGloas,
6
6
  ForkPreDeneb,
7
+ MAX_BUILDER_ENTRIES,
8
+ MAX_BUILDER_PUBKEYS,
9
+ MAX_BUILDER_URL_SIZE,
7
10
  MIN_SEED_LOOKAHEAD,
8
11
  SLOTS_PER_EPOCH,
9
12
  VALIDATOR_REGISTRY_LIMIT,
@@ -76,15 +79,21 @@ export type ExtraProduceBlockOpts = {
76
79
  blindedLocal?: boolean;
77
80
  };
78
81
 
82
+ /** Lodestar-specific (non-standardized) options */
83
+ export type ExtraProduceBlockV4Opts = {
84
+ feeRecipient?: string;
85
+ strictFeeRecipientCheck?: boolean;
86
+ };
87
+
79
88
  export const ProduceBlockV3MetaType = new ContainerType(
80
89
  {
81
90
  ...VersionType.fields,
82
91
  /** Specifies whether the response contains full or blinded block */
83
92
  executionPayloadBlinded: ssz.Boolean,
84
93
  /** Execution payload value in Wei */
85
- executionPayloadValue: ssz.UintBn64,
94
+ executionPayloadValue: ssz.Wei,
86
95
  /** Consensus rewards paid to the proposer for this block, in Wei */
87
- consensusBlockValue: ssz.UintBn64,
96
+ consensusBlockValue: ssz.Wei,
88
97
  },
89
98
  {jsonCase: "eth2"}
90
99
  );
@@ -98,16 +107,19 @@ export const ProduceBlockV4MetaType = new ContainerType(
98
107
  {
99
108
  ...VersionType.fields,
100
109
  /** Consensus rewards paid to the proposer for this block, in Wei */
101
- consensusBlockValue: ssz.UintBn64,
110
+ consensusBlockValue: ssz.Wei,
102
111
  /** Local execution payload value when self-building, or builder bid value when committing to a bid, in Wei */
103
- executionPayloadValue: ssz.UintBn64,
112
+ executionPayloadValue: ssz.Wei,
104
113
  /** Specifies whether the response contains full block contents or only the beacon block */
105
114
  executionPayloadIncluded: ssz.Boolean,
106
115
  },
107
116
  {jsonCase: "eth2"}
108
117
  );
109
118
 
110
- export type ProduceBlockV4Meta = ValueOf<typeof ProduceBlockV4MetaType>;
119
+ export type ProduceBlockV4Meta = ValueOf<typeof ProduceBlockV4MetaType> & {
120
+ /** The url of the winning builder when the bid came through the builder API channel */
121
+ builderUrl?: string;
122
+ };
111
123
 
112
124
  export const AttesterDutyType = new ContainerType(
113
125
  {
@@ -258,6 +270,59 @@ export const SignedProposerPreferencesListType = ArrayOf(
258
270
  (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
259
271
  );
260
272
 
273
+ // The url is UTF-8 bytes on the SSZ wire but a plain string in JSON
274
+ class BuilderUrlType extends ByteListType {
275
+ fromJson(json: unknown): Uint8Array {
276
+ if (typeof json !== "string") {
277
+ throw Error("Builder url must be a string");
278
+ }
279
+ const value = new TextEncoder().encode(json);
280
+ if (value.length > this.limitBytes) {
281
+ throw Error(`Builder url must not exceed ${this.limitBytes} bytes`);
282
+ }
283
+ return value;
284
+ }
285
+ toJson(value: Uint8Array): unknown {
286
+ return new TextDecoder("utf8", {fatal: true}).decode(value);
287
+ }
288
+ }
289
+
290
+ export const BuilderEntryType = new ContainerType(
291
+ {
292
+ url: new BuilderUrlType(MAX_BUILDER_URL_SIZE),
293
+ auth: ssz.gloas.SignedBuilderRequestAuth,
294
+ builderPubkeys: ArrayOf(ssz.BLSPubkey, MAX_BUILDER_PUBKEYS),
295
+ maxExecutionPayment: ssz.Gwei,
296
+ minBid: ssz.Gwei,
297
+ builderBoostFactor: ssz.UintBn64,
298
+ },
299
+ {typeName: "BuilderEntry", jsonCase: "eth2"}
300
+ );
301
+
302
+ export const BuilderConfigType = new ContainerType(
303
+ {
304
+ minBid: ssz.Gwei,
305
+ builderBoostFactor: ssz.UintBn64,
306
+ builders: ArrayOf(BuilderEntryType, MAX_BUILDER_ENTRIES),
307
+ },
308
+ {typeName: "BuilderConfig", jsonCase: "eth2"}
309
+ );
310
+
311
+ export const BuilderPreferencesEntryType = new ContainerType(
312
+ {
313
+ proposerPubkey: ssz.BLSPubkey,
314
+ url: new BuilderUrlType(MAX_BUILDER_URL_SIZE),
315
+ auth: ssz.gloas.SignedBuilderRequestAuth,
316
+ maxExecutionPayment: ssz.Gwei,
317
+ },
318
+ {typeName: "BuilderPreferencesEntry", jsonCase: "eth2"}
319
+ );
320
+
321
+ export const BuilderPreferencesEntryListType = ArrayOf(
322
+ BuilderPreferencesEntryType,
323
+ MAX_BUILDER_ENTRIES * (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
324
+ );
325
+
261
326
  export type ValidatorIndices = ValueOf<typeof ValidatorIndicesType>;
262
327
  export type AttesterDuty = ValueOf<typeof AttesterDutyType>;
263
328
  export type AttesterDutyList = ValueOf<typeof AttesterDutyListType>;
@@ -285,6 +350,10 @@ export type LivenessResponseData = ValueOf<typeof LivenessResponseDataType>;
285
350
  export type LivenessResponseDataList = ValueOf<typeof LivenessResponseDataListType>;
286
351
  export type SignedValidatorRegistrationV1List = ValueOf<typeof SignedValidatorRegistrationV1ListType>;
287
352
  export type SignedProposerPreferencesList = ValueOf<typeof SignedProposerPreferencesListType>;
353
+ export type BuilderEntry = ValueOf<typeof BuilderEntryType>;
354
+ export type BuilderConfig = ValueOf<typeof BuilderConfigType>;
355
+ export type BuilderPreferencesEntry = ValueOf<typeof BuilderPreferencesEntryType>;
356
+ export type BuilderPreferencesEntryList = ValueOf<typeof BuilderPreferencesEntryListType>;
288
357
 
289
358
  // The beacon node does not return any data if there is no canonical block at the requested slot (missed slot).
290
359
  // In this case, we receive a success response (204) which is not handled as an error. The generic response
@@ -427,6 +496,9 @@ export type Endpoints = {
427
496
  * so there is no longer a concept of blinded or unblinded blocks. Builders release the payload later.
428
497
  * This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks.
429
498
  *
499
+ * The required `BuilderConfig` request body carries the builder entries to solicit builder API
500
+ * bids from, plus the top-level `minBid` and `builderBoostFactor` that apply to p2p bids.
501
+ *
430
502
  * When self-building and `includePayload` is true, the response contains the full `BlockContents`
431
503
  * (block, execution payload envelope, KZG proofs and blobs) which enables stateless envelope
432
504
  * publishing via any beacon node. When `includePayload` is false, only the `BeaconBlock` is
@@ -434,7 +506,7 @@ export type Endpoints = {
434
506
  * When committing to a builder bid, only the `BeaconBlock` is returned in either case.
435
507
  */
436
508
  produceBlockV4: Endpoint<
437
- "GET",
509
+ "POST",
438
510
  {
439
511
  /** The slot for which the block should be proposed */
440
512
  slot: Slot;
@@ -443,10 +515,10 @@ export type Endpoints = {
443
515
  /** Arbitrary data validator wants to include in block */
444
516
  graffiti?: string;
445
517
  skipRandaoVerification?: boolean;
446
- builderBoostFactor?: UintBn64;
447
518
  /** Include execution payload envelope and blobs in the response when self-building */
448
519
  includePayload: boolean;
449
- } & Omit<ExtraProduceBlockOpts, "blindedLocal">,
520
+ builderConfig: BuilderConfig;
521
+ } & ExtraProduceBlockV4Opts,
450
522
  {
451
523
  params: {slot: number};
452
524
  query: {
@@ -454,11 +526,11 @@ export type Endpoints = {
454
526
  graffiti?: string;
455
527
  skip_randao_verification?: string;
456
528
  fee_recipient?: string;
457
- builder_selection?: string;
458
- builder_boost_factor?: string;
459
529
  strict_fee_recipient_check?: boolean;
460
530
  include_payload: boolean;
461
531
  };
532
+ body: unknown;
533
+ headers: {[MetaHeader.Version]: string};
462
534
  },
463
535
  BeaconBlock<ForkPostGloas> | BlockContents<ForkPostGloas>,
464
536
  ProduceBlockV4Meta
@@ -688,6 +760,19 @@ export type Endpoints = {
688
760
  EmptyResponseData,
689
761
  EmptyMeta
690
762
  >;
763
+
764
+ /**
765
+ * Submit per-builder preferences for one or more proposers ahead of their proposal slot.
766
+ * The beacon node submits each entry to the builder API endpoint at the entry's url so
767
+ * builders hold the preferences before the bid request arrives.
768
+ */
769
+ submitBuilderPreferences: Endpoint<
770
+ "POST",
771
+ {builderPreferences: BuilderPreferencesEntryList},
772
+ {body: unknown; headers: {[MetaHeader.Version]: string}},
773
+ EmptyResponseData,
774
+ EmptyMeta
775
+ >;
691
776
  };
692
777
 
693
778
  export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
@@ -910,18 +995,17 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
910
995
  },
911
996
  produceBlockV4: {
912
997
  url: "/eth/v4/validator/blocks/{slot}",
913
- method: "GET",
998
+ method: "POST",
914
999
  req: {
915
- writeReq: ({
1000
+ writeReqJson: ({
916
1001
  slot,
917
1002
  randaoReveal,
918
1003
  graffiti,
919
1004
  skipRandaoVerification,
920
1005
  feeRecipient,
921
- builderSelection,
922
- builderBoostFactor,
923
1006
  strictFeeRecipientCheck,
924
1007
  includePayload,
1008
+ builderConfig,
925
1009
  }) => ({
926
1010
  params: {slot},
927
1011
  query: {
@@ -929,23 +1013,60 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
929
1013
  graffiti: toGraffitiHex(graffiti),
930
1014
  skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification),
931
1015
  fee_recipient: feeRecipient,
932
- builder_selection: builderSelection,
933
- builder_boost_factor: builderBoostFactor?.toString(),
934
1016
  strict_fee_recipient_check: strictFeeRecipientCheck,
935
1017
  include_payload: includePayload,
936
1018
  },
1019
+ body: BuilderConfigType.toJson(builderConfig),
1020
+ headers: {[MetaHeader.Version]: config.getForkName(slot)},
937
1021
  }),
938
- parseReq: ({params, query}) => ({
939
- slot: params.slot,
940
- randaoReveal: fromHex(query.randao_reveal),
941
- graffiti: fromGraffitiHex(query.graffiti),
942
- skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification),
943
- feeRecipient: query.fee_recipient,
944
- builderSelection: query.builder_selection as BuilderSelection,
945
- builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor),
946
- strictFeeRecipientCheck: query.strict_fee_recipient_check,
947
- includePayload: query.include_payload,
1022
+ parseReqJson: ({params, query, body, headers}) => {
1023
+ toForkName(fromHeaders(headers, MetaHeader.Version));
1024
+ return {
1025
+ slot: params.slot,
1026
+ randaoReveal: fromHex(query.randao_reveal),
1027
+ graffiti: fromGraffitiHex(query.graffiti),
1028
+ skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification),
1029
+ feeRecipient: query.fee_recipient,
1030
+ strictFeeRecipientCheck: query.strict_fee_recipient_check,
1031
+ includePayload: query.include_payload,
1032
+ builderConfig: BuilderConfigType.fromJson(body),
1033
+ };
1034
+ },
1035
+ writeReqSsz: ({
1036
+ slot,
1037
+ randaoReveal,
1038
+ graffiti,
1039
+ skipRandaoVerification,
1040
+ feeRecipient,
1041
+ strictFeeRecipientCheck,
1042
+ includePayload,
1043
+ builderConfig,
1044
+ }) => ({
1045
+ params: {slot},
1046
+ query: {
1047
+ randao_reveal: toHex(randaoReveal),
1048
+ graffiti: toGraffitiHex(graffiti),
1049
+ skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification),
1050
+ fee_recipient: feeRecipient,
1051
+ strict_fee_recipient_check: strictFeeRecipientCheck,
1052
+ include_payload: includePayload,
1053
+ },
1054
+ body: BuilderConfigType.serialize(builderConfig),
1055
+ headers: {[MetaHeader.Version]: config.getForkName(slot)},
948
1056
  }),
1057
+ parseReqSsz: ({params, query, body, headers}) => {
1058
+ toForkName(fromHeaders(headers, MetaHeader.Version));
1059
+ return {
1060
+ slot: params.slot,
1061
+ randaoReveal: fromHex(query.randao_reveal),
1062
+ graffiti: fromGraffitiHex(query.graffiti),
1063
+ skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification),
1064
+ feeRecipient: query.fee_recipient,
1065
+ strictFeeRecipientCheck: query.strict_fee_recipient_check,
1066
+ includePayload: query.include_payload,
1067
+ builderConfig: BuilderConfigType.deserialize(body),
1068
+ };
1069
+ },
949
1070
  schema: {
950
1071
  params: {slot: Schema.UintRequired},
951
1072
  query: {
@@ -953,13 +1074,16 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
953
1074
  graffiti: Schema.String,
954
1075
  skip_randao_verification: Schema.String,
955
1076
  fee_recipient: Schema.String,
956
- builder_selection: Schema.String,
957
- builder_boost_factor: Schema.String,
958
1077
  strict_fee_recipient_check: Schema.Boolean,
959
1078
  include_payload: Schema.BooleanRequired,
960
1079
  },
1080
+ body: Schema.Object,
1081
+ headers: {[MetaHeader.Version]: Schema.String},
961
1082
  },
962
1083
  },
1084
+ init: {
1085
+ requestWireFormat: WireFormat.ssz,
1086
+ },
963
1087
  resp: {
964
1088
  data: WithMeta(
965
1089
  ({version, executionPayloadIncluded}) =>
@@ -971,18 +1095,23 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
971
1095
  ),
972
1096
  meta: {
973
1097
  toJson: (meta) => ProduceBlockV4MetaType.toJson(meta),
974
- fromJson: (val) => ProduceBlockV4MetaType.fromJson(val),
1098
+ fromJson: (val, headers) => ({
1099
+ ...ProduceBlockV4MetaType.fromJson(val),
1100
+ builderUrl: headers?.get(MetaHeader.BuilderUrl) ?? undefined,
1101
+ }),
975
1102
  toHeadersObject: (meta) => ({
976
1103
  [MetaHeader.Version]: meta.version,
977
1104
  [MetaHeader.ConsensusBlockValue]: meta.consensusBlockValue.toString(),
978
1105
  [MetaHeader.ExecutionPayloadValue]: meta.executionPayloadValue.toString(),
979
1106
  [MetaHeader.ExecutionPayloadIncluded]: meta.executionPayloadIncluded.toString(),
1107
+ ...(meta.builderUrl !== undefined ? {[MetaHeader.BuilderUrl]: meta.builderUrl} : {}),
980
1108
  }),
981
1109
  fromHeaders: (headers) => ({
982
1110
  version: toForkName(headers.getRequired(MetaHeader.Version)),
983
1111
  consensusBlockValue: BigInt(headers.getRequired(MetaHeader.ConsensusBlockValue)),
984
1112
  executionPayloadValue: BigInt(headers.getRequired(MetaHeader.ExecutionPayloadValue)),
985
1113
  executionPayloadIncluded: toBoolean(headers.getRequired(MetaHeader.ExecutionPayloadIncluded)),
1114
+ builderUrl: headers.get(MetaHeader.BuilderUrl) ?? undefined,
986
1115
  }),
987
1116
  },
988
1117
  },
@@ -1294,6 +1423,36 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
1294
1423
  },
1295
1424
  resp: EmptyResponseCodec,
1296
1425
  },
1426
+ submitBuilderPreferences: {
1427
+ url: "/eth/v1/validator/builder_preferences",
1428
+ method: "POST",
1429
+ req: {
1430
+ writeReqJson: ({builderPreferences}) => ({
1431
+ body: BuilderPreferencesEntryListType.toJson(builderPreferences),
1432
+ headers: {[MetaHeader.Version]: config.getForkName(builderPreferences[0]?.auth.message.slot ?? 0)},
1433
+ }),
1434
+ parseReqJson: ({body, headers}) => {
1435
+ toForkName(fromHeaders(headers, MetaHeader.Version));
1436
+ return {builderPreferences: BuilderPreferencesEntryListType.fromJson(body)};
1437
+ },
1438
+ writeReqSsz: ({builderPreferences}) => ({
1439
+ body: BuilderPreferencesEntryListType.serialize(builderPreferences),
1440
+ headers: {[MetaHeader.Version]: config.getForkName(builderPreferences[0]?.auth.message.slot ?? 0)},
1441
+ }),
1442
+ parseReqSsz: ({body, headers}) => {
1443
+ toForkName(fromHeaders(headers, MetaHeader.Version));
1444
+ return {builderPreferences: BuilderPreferencesEntryListType.deserialize(body)};
1445
+ },
1446
+ schema: {
1447
+ body: Schema.ObjectArray,
1448
+ headers: {[MetaHeader.Version]: Schema.String},
1449
+ },
1450
+ },
1451
+ resp: EmptyResponseCodec,
1452
+ init: {
1453
+ requestWireFormat: WireFormat.ssz,
1454
+ },
1455
+ },
1297
1456
  };
1298
1457
  }
1299
1458
 
@@ -42,10 +42,15 @@ export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods<E
42
42
  topics: req.query.topics,
43
43
  signal: controller.signal,
44
44
  onEvent: (event) => {
45
+ // Serialization is per event and must not tear down the stream. Throwing here is
46
+ // handled by the caller which logs it and keeps the subscription alive, the client
47
+ // only misses this single event instead of silently losing the whole stream.
48
+ const data = eventSerdes.toJson(event);
49
+
45
50
  try {
46
- const data = eventSerdes.toJson(event);
47
51
  res.raw.write(serializeSSEEvent({event: event.type, data}));
48
52
  } catch (e) {
53
+ // The connection itself is broken, there is no way to recover it
49
54
  reject(e);
50
55
  }
51
56
  },
@@ -58,10 +63,12 @@ export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods<E
58
63
  req.socket.once("close", () => resolve());
59
64
  req.socket.once("end", () => resolve());
60
65
  });
61
-
62
- // api.eventstream will never stop, so no need to ever call `res.raw.end();`
63
66
  } finally {
64
67
  controller.abort();
68
+ // Always end the response. If an error is thrown after the headers were sent, fastify can
69
+ // no longer write a status code and would leave the socket open, the client then waits
70
+ // forever on a stream that will never emit another event.
71
+ if (!res.raw.writableEnded && !res.raw.destroyed) res.raw.end();
65
72
  }
66
73
  },
67
74
  },
@@ -1,16 +1,18 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {ForkName, VALIDATOR_REGISTRY_LIMIT, isForkPostDeneb} from "@lodestar/params";
2
+ import {ForkName, ForkPostGloas, VALIDATOR_REGISTRY_LIMIT, isForkPostDeneb} from "@lodestar/params";
3
3
  import {
4
4
  ArrayOf,
5
5
  BLSPubkey,
6
6
  ExecutionPayload,
7
7
  ExecutionPayloadAndBlobsBundle,
8
8
  Root,
9
+ SignedBeaconBlock,
9
10
  SignedBlindedBeaconBlock,
10
11
  SignedBuilderBid,
11
12
  Slot,
12
13
  WithOptionalBytes,
13
14
  bellatrix,
15
+ gloas,
14
16
  ssz,
15
17
  } from "@lodestar/types";
16
18
  import {fromHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
@@ -23,7 +25,7 @@ import {
23
25
  EmptyResponseData,
24
26
  WithVersion,
25
27
  } from "../utils/codecs.js";
26
- import {getPostBellatrixForkTypes, getPostDenebForkTypes, toForkName} from "../utils/fork.js";
28
+ import {getPostBellatrixForkTypes, getPostDenebForkTypes, getPostGloasForkTypes, toForkName} from "../utils/fork.js";
27
29
  import {fromHeaders} from "../utils/headers.js";
28
30
  import {Endpoint, RouteDefinitions, Schema} from "../utils/index.js";
29
31
  import {MetaHeader, VersionCodec, VersionMeta} from "../utils/metadata.js";
@@ -35,6 +37,9 @@ import {WireFormat} from "../utils/wireFormat.js";
35
37
  // It is important that this type indicates that there might be no value to ensure it is properly handled downstream.
36
38
  export type MaybeSignedBuilderBid = SignedBuilderBid | undefined;
37
39
 
40
+ // Same as `MaybeSignedBuilderBid`, the builder responds with 204 if no bid is available
41
+ export type MaybeSignedExecutionPayloadBid = gloas.SignedExecutionPayloadBid | undefined;
42
+
38
43
  const RegistrationsType = ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1, VALIDATOR_REGISTRY_LIMIT);
39
44
 
40
45
  export type Endpoints = {
@@ -82,6 +87,45 @@ export type Endpoints = {
82
87
  EmptyResponseData,
83
88
  EmptyMeta
84
89
  >;
90
+
91
+ getExecutionPayloadBid: Endpoint<
92
+ "POST",
93
+ {
94
+ slot: Slot;
95
+ parentHash: Root;
96
+ parentRoot: Root;
97
+ proposerPubkey: BLSPubkey;
98
+ /** Authenticates the requesting proposer to the builder */
99
+ requestAuth: gloas.SignedBuilderRequestAuth;
100
+ /** Unix timestamp in milliseconds at which the request was sent */
101
+ dateMilliseconds: number;
102
+ /** The proposer's timeout for the request in milliseconds, measured from `dateMilliseconds` */
103
+ timeoutMs: number;
104
+ },
105
+ {
106
+ params: {slot: Slot; parent_hash: string; parent_root: string; proposer_pubkey: string};
107
+ body: unknown;
108
+ headers: {[MetaHeader.Version]: string; [MetaHeader.DateMilliseconds]: string; [MetaHeader.TimeoutMs]: string};
109
+ },
110
+ MaybeSignedExecutionPayloadBid,
111
+ VersionMeta
112
+ >;
113
+
114
+ submitSignedBeaconBlock: Endpoint<
115
+ "POST",
116
+ {signedBlock: WithOptionalBytes<SignedBeaconBlock<ForkPostGloas>>},
117
+ {body: unknown; headers: {[MetaHeader.Version]: string}},
118
+ EmptyResponseData,
119
+ EmptyMeta
120
+ >;
121
+
122
+ submitBuilderPreferences: Endpoint<
123
+ "POST",
124
+ {proposerPubkey: BLSPubkey; request: gloas.BuilderPreferencesRequest},
125
+ {params: {proposer_pubkey: string}; body: unknown; headers: {[MetaHeader.Version]: string}},
126
+ EmptyResponseData,
127
+ EmptyMeta
128
+ >;
85
129
  };
86
130
 
87
131
  export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
@@ -223,5 +267,186 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
223
267
  },
224
268
  resp: EmptyResponseCodec,
225
269
  },
270
+ getExecutionPayloadBid: {
271
+ url: "/eth/v1/builder/execution_payload_bid/{slot}/{parent_hash}/{parent_root}/{proposer_pubkey}",
272
+ method: "POST",
273
+ req: {
274
+ writeReqJson: ({slot, parentHash, parentRoot, proposerPubkey, requestAuth, dateMilliseconds, timeoutMs}) => ({
275
+ params: {
276
+ slot,
277
+ parent_hash: toRootHex(parentHash),
278
+ parent_root: toRootHex(parentRoot),
279
+ proposer_pubkey: toPubkeyHex(proposerPubkey),
280
+ },
281
+ body: ssz.gloas.SignedBuilderRequestAuth.toJson(requestAuth),
282
+ headers: {
283
+ [MetaHeader.Version]: config.getForkName(slot),
284
+ [MetaHeader.DateMilliseconds]: dateMilliseconds.toString(),
285
+ [MetaHeader.TimeoutMs]: timeoutMs.toString(),
286
+ },
287
+ }),
288
+ parseReqJson: ({params, body, headers}) => {
289
+ toForkName(fromHeaders(headers, MetaHeader.Version));
290
+ return {
291
+ slot: params.slot,
292
+ parentHash: fromHex(params.parent_hash),
293
+ parentRoot: fromHex(params.parent_root),
294
+ proposerPubkey: fromHex(params.proposer_pubkey),
295
+ requestAuth: ssz.gloas.SignedBuilderRequestAuth.fromJson(body),
296
+ dateMilliseconds: parseRequiredUintHeader(
297
+ fromHeaders(headers, MetaHeader.DateMilliseconds),
298
+ MetaHeader.DateMilliseconds
299
+ ),
300
+ timeoutMs: parseRequiredUintHeader(fromHeaders(headers, MetaHeader.TimeoutMs), MetaHeader.TimeoutMs),
301
+ };
302
+ },
303
+ writeReqSsz: ({slot, parentHash, parentRoot, proposerPubkey, requestAuth, dateMilliseconds, timeoutMs}) => ({
304
+ params: {
305
+ slot,
306
+ parent_hash: toRootHex(parentHash),
307
+ parent_root: toRootHex(parentRoot),
308
+ proposer_pubkey: toPubkeyHex(proposerPubkey),
309
+ },
310
+ body: ssz.gloas.SignedBuilderRequestAuth.serialize(requestAuth),
311
+ headers: {
312
+ [MetaHeader.Version]: config.getForkName(slot),
313
+ [MetaHeader.DateMilliseconds]: dateMilliseconds.toString(),
314
+ [MetaHeader.TimeoutMs]: timeoutMs.toString(),
315
+ },
316
+ }),
317
+ parseReqSsz: ({params, body, headers}) => {
318
+ toForkName(fromHeaders(headers, MetaHeader.Version));
319
+ return {
320
+ slot: params.slot,
321
+ parentHash: fromHex(params.parent_hash),
322
+ parentRoot: fromHex(params.parent_root),
323
+ proposerPubkey: fromHex(params.proposer_pubkey),
324
+ requestAuth: ssz.gloas.SignedBuilderRequestAuth.deserialize(body),
325
+ dateMilliseconds: parseRequiredUintHeader(
326
+ fromHeaders(headers, MetaHeader.DateMilliseconds),
327
+ MetaHeader.DateMilliseconds
328
+ ),
329
+ timeoutMs: parseRequiredUintHeader(fromHeaders(headers, MetaHeader.TimeoutMs), MetaHeader.TimeoutMs),
330
+ };
331
+ },
332
+ schema: {
333
+ params: {
334
+ slot: Schema.UintRequired,
335
+ parent_hash: Schema.StringRequired,
336
+ parent_root: Schema.StringRequired,
337
+ proposer_pubkey: Schema.StringRequired,
338
+ },
339
+ body: Schema.Object,
340
+ headers: {
341
+ [MetaHeader.Version]: Schema.String,
342
+ [MetaHeader.DateMilliseconds]: Schema.String,
343
+ [MetaHeader.TimeoutMs]: Schema.String,
344
+ },
345
+ },
346
+ },
347
+ resp: {
348
+ data: WithVersion<MaybeSignedExecutionPayloadBid, VersionMeta>(
349
+ (fork: ForkName) => getPostGloasForkTypes(fork).SignedExecutionPayloadBid
350
+ ),
351
+ meta: VersionCodec,
352
+ },
353
+ init: {
354
+ requestWireFormat: WireFormat.ssz,
355
+ },
356
+ },
357
+ submitSignedBeaconBlock: {
358
+ url: "/eth/v1/builder/beacon_blocks",
359
+ method: "POST",
360
+ req: {
361
+ writeReqJson: ({signedBlock}) => {
362
+ const fork = config.getForkName(signedBlock.data.message.slot);
363
+ return {
364
+ body: getPostGloasForkTypes(fork).SignedBeaconBlock.toJson(signedBlock.data),
365
+ headers: {
366
+ [MetaHeader.Version]: fork,
367
+ },
368
+ };
369
+ },
370
+ parseReqJson: ({body, headers}) => {
371
+ const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
372
+ return {
373
+ signedBlock: {data: getPostGloasForkTypes(fork).SignedBeaconBlock.fromJson(body)},
374
+ };
375
+ },
376
+ writeReqSsz: ({signedBlock}) => {
377
+ const fork = config.getForkName(signedBlock.data.message.slot);
378
+ return {
379
+ body: signedBlock.bytes ?? getPostGloasForkTypes(fork).SignedBeaconBlock.serialize(signedBlock.data),
380
+ headers: {
381
+ [MetaHeader.Version]: fork,
382
+ },
383
+ };
384
+ },
385
+ parseReqSsz: ({body, headers}) => {
386
+ const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
387
+ return {
388
+ signedBlock: {data: getPostGloasForkTypes(fork).SignedBeaconBlock.deserialize(body)},
389
+ };
390
+ },
391
+ schema: {
392
+ body: Schema.Object,
393
+ headers: {[MetaHeader.Version]: Schema.String},
394
+ },
395
+ },
396
+ resp: EmptyResponseCodec,
397
+ init: {
398
+ requestWireFormat: WireFormat.ssz,
399
+ },
400
+ },
401
+ submitBuilderPreferences: {
402
+ url: "/eth/v1/builder/builder_preferences/{proposer_pubkey}",
403
+ method: "POST",
404
+ req: {
405
+ writeReqJson: ({proposerPubkey, request}) => ({
406
+ params: {proposer_pubkey: toPubkeyHex(proposerPubkey)},
407
+ body: ssz.gloas.BuilderPreferencesRequest.toJson(request),
408
+ headers: {[MetaHeader.Version]: config.getForkName(request.auth.message.slot)},
409
+ }),
410
+ parseReqJson: ({params, body, headers}) => {
411
+ toForkName(fromHeaders(headers, MetaHeader.Version));
412
+ return {
413
+ proposerPubkey: fromHex(params.proposer_pubkey),
414
+ request: ssz.gloas.BuilderPreferencesRequest.fromJson(body),
415
+ };
416
+ },
417
+ writeReqSsz: ({proposerPubkey, request}) => ({
418
+ params: {proposer_pubkey: toPubkeyHex(proposerPubkey)},
419
+ body: ssz.gloas.BuilderPreferencesRequest.serialize(request),
420
+ headers: {[MetaHeader.Version]: config.getForkName(request.auth.message.slot)},
421
+ }),
422
+ parseReqSsz: ({params, body, headers}) => {
423
+ toForkName(fromHeaders(headers, MetaHeader.Version));
424
+ return {
425
+ proposerPubkey: fromHex(params.proposer_pubkey),
426
+ request: ssz.gloas.BuilderPreferencesRequest.deserialize(body),
427
+ };
428
+ },
429
+ schema: {
430
+ params: {proposer_pubkey: Schema.StringRequired},
431
+ body: Schema.Object,
432
+ headers: {[MetaHeader.Version]: Schema.String},
433
+ },
434
+ },
435
+ resp: EmptyResponseCodec,
436
+ init: {
437
+ requestWireFormat: WireFormat.ssz,
438
+ },
439
+ },
226
440
  };
227
441
  }
442
+
443
+ function parseRequiredUintHeader(value: string, header: MetaHeader): number {
444
+ if (!/^\d+$/.test(value)) {
445
+ throw Error(`${header} must be a non-negative integer`);
446
+ }
447
+ const parsed = Number(value);
448
+ if (!Number.isSafeInteger(parsed)) {
449
+ throw Error(`${header} must be a safe integer`);
450
+ }
451
+ return parsed;
452
+ }
@@ -7,6 +7,8 @@ import * as keymanager from "./client.js";
7
7
 
8
8
  export type {
9
9
  BuilderBoostFactorData,
10
+ BuilderConfigData,
11
+ BuilderEntryConfig,
10
12
  Endpoints,
11
13
  FeeRecipientData,
12
14
  GasLimitData,
@@ -19,7 +21,13 @@ export type {
19
21
  SignerDefinition,
20
22
  SlashingProtectionData,
21
23
  } from "./routes.js";
22
- export {DeleteRemoteKeyStatus, DeletionStatus, ImportRemoteKeyStatus, ImportStatus} from "./routes.js";
24
+ export {
25
+ DeleteRemoteKeyStatus,
26
+ DeletionStatus,
27
+ ImportRemoteKeyStatus,
28
+ ImportStatus,
29
+ builderConfigDataFromJson,
30
+ } from "./routes.js";
23
31
 
24
32
  export type {ApiClient};
25
33