@lodestar/api 1.50.0-dev.2e668c516f → 1.50.0-dev.3430a7cf04

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 (52) hide show
  1. package/lib/beacon/client/events.d.ts.map +1 -1
  2. package/lib/beacon/client/events.js +2 -1
  3. package/lib/beacon/client/events.js.map +1 -1
  4. package/lib/beacon/routes/beacon/block.d.ts.map +1 -1
  5. package/lib/beacon/routes/beacon/block.js +3 -3
  6. package/lib/beacon/routes/beacon/block.js.map +1 -1
  7. package/lib/beacon/routes/beacon/index.d.ts +28 -2
  8. package/lib/beacon/routes/beacon/index.d.ts.map +1 -1
  9. package/lib/beacon/routes/beacon/index.js +23 -1
  10. package/lib/beacon/routes/beacon/index.js.map +1 -1
  11. package/lib/beacon/routes/beacon/state.d.ts +30 -5
  12. package/lib/beacon/routes/beacon/state.d.ts.map +1 -1
  13. package/lib/beacon/routes/beacon/state.js +25 -4
  14. package/lib/beacon/routes/beacon/state.js.map +1 -1
  15. package/lib/beacon/routes/debug.d.ts +24 -10
  16. package/lib/beacon/routes/debug.d.ts.map +1 -1
  17. package/lib/beacon/routes/debug.js +19 -18
  18. package/lib/beacon/routes/debug.js.map +1 -1
  19. package/lib/beacon/routes/events.d.ts +18 -5
  20. package/lib/beacon/routes/events.d.ts.map +1 -1
  21. package/lib/beacon/routes/events.js +28 -5
  22. package/lib/beacon/routes/events.js.map +1 -1
  23. package/lib/beacon/routes/lodestar.d.ts.map +1 -1
  24. package/lib/beacon/routes/lodestar.js +5 -5
  25. package/lib/beacon/routes/lodestar.js.map +1 -1
  26. package/lib/index.d.ts +1 -1
  27. package/lib/index.d.ts.map +1 -1
  28. package/lib/index.js.map +1 -1
  29. package/lib/utils/client/error.d.ts +8 -1
  30. package/lib/utils/client/error.d.ts.map +1 -1
  31. package/lib/utils/client/error.js +4 -1
  32. package/lib/utils/client/error.js.map +1 -1
  33. package/lib/utils/client/response.d.ts +1 -1
  34. package/lib/utils/client/response.d.ts.map +1 -1
  35. package/lib/utils/client/response.js +11 -7
  36. package/lib/utils/client/response.js.map +1 -1
  37. package/lib/utils/fork.d.ts +2 -1
  38. package/lib/utils/fork.d.ts.map +1 -1
  39. package/lib/utils/fork.js +7 -1
  40. package/lib/utils/fork.js.map +1 -1
  41. package/package.json +7 -7
  42. package/src/beacon/client/events.ts +3 -0
  43. package/src/beacon/routes/beacon/block.ts +10 -5
  44. package/src/beacon/routes/beacon/index.ts +40 -1
  45. package/src/beacon/routes/beacon/state.ts +50 -8
  46. package/src/beacon/routes/debug.ts +22 -18
  47. package/src/beacon/routes/events.ts +39 -12
  48. package/src/beacon/routes/lodestar.ts +5 -4
  49. package/src/index.ts +1 -1
  50. package/src/utils/client/error.ts +7 -1
  51. package/src/utils/client/response.ts +13 -9
  52. package/src/utils/fork.ts +10 -0
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.50.0-dev.2e668c516f",
14
+ "version": "1.50.0-dev.3430a7cf04",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -75,11 +75,11 @@
75
75
  },
76
76
  "dependencies": {
77
77
  "@chainsafe/persistent-merkle-tree": "^1.3.1",
78
- "@chainsafe/ssz": "^1.8.0",
79
- "@lodestar/config": "^1.50.0-dev.2e668c516f",
80
- "@lodestar/params": "^1.50.0-dev.2e668c516f",
81
- "@lodestar/types": "^1.50.0-dev.2e668c516f",
82
- "@lodestar/utils": "^1.50.0-dev.2e668c516f",
78
+ "@chainsafe/ssz": "^1.8.1",
79
+ "@lodestar/config": "^1.50.0-dev.3430a7cf04",
80
+ "@lodestar/params": "^1.50.0-dev.3430a7cf04",
81
+ "@lodestar/types": "^1.50.0-dev.3430a7cf04",
82
+ "@lodestar/utils": "^1.50.0-dev.3430a7cf04",
83
83
  "eventsource": "^2.0.2",
84
84
  "qs": "^6.14.2"
85
85
  },
@@ -96,5 +96,5 @@
96
96
  "api",
97
97
  "blockchain"
98
98
  ],
99
- "gitHead": "64f9950e4dda48c3c64de86ad80b01ad8274f696"
99
+ "gitHead": "2dcdaf9bccdc715d476f5f0a4a347a4c4c70934a"
100
100
  }
@@ -21,6 +21,7 @@ export function getClient(config: ChainForkConfig, baseUrl: string): ApiClient {
21
21
  topics,
22
22
  signal,
23
23
  onEvent,
24
+ onOpen,
24
25
  onError,
25
26
  onClose,
26
27
  }): Promise<ApiResponse<Endpoints["eventstream"]>> => {
@@ -39,6 +40,8 @@ export function getClient(config: ChainForkConfig, baseUrl: string): ApiClient {
39
40
  };
40
41
  signal.addEventListener("abort", close, {once: true});
41
42
 
43
+ eventSource.onopen = () => onOpen?.();
44
+
42
45
  for (const topic of topics) {
43
46
  eventSource.addEventListener(topic, (event: MessageEvent) => {
44
47
  try {
@@ -5,6 +5,7 @@ import {
5
5
  ForkPostGloas,
6
6
  ForkPreBellatrix,
7
7
  ForkPreDeneb,
8
+ ForkPreGloas,
8
9
  isForkPostBellatrix,
9
10
  isForkPostDeneb,
10
11
  isForkPostGloas,
@@ -296,7 +297,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
296
297
  method: "GET",
297
298
  req: blockIdOnlyReq,
298
299
  resp: {
299
- data: WithVersion((fork) => ssz[fork].SignedBeaconBlock),
300
+ data: WithVersion((fork) => sszTypesFor(fork).SignedBeaconBlock),
300
301
  meta: ExecutionOptimisticFinalizedAndVersionCodec,
301
302
  },
302
303
  },
@@ -362,7 +363,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
362
363
  body:
363
364
  isForkPostDeneb(fork) && !isForkPostGloas(fork)
364
365
  ? sszTypesFor(fork).SignedBlockContents.toJson(
365
- signedBlockContents as SignedBlockContents<ForkPostDeneb>
366
+ signedBlockContents as SignedBlockContents<ForkPostDeneb & ForkPreGloas>
366
367
  )
367
368
  : sszTypesFor(fork).SignedBeaconBlock.toJson(
368
369
  signedBlockContents.signedBlock as SignedBeaconBlock<ForkPreDeneb | ForkPostGloas>
@@ -380,7 +381,9 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
380
381
  signedBlockContents:
381
382
  isForkPostDeneb(forkName) && !isForkPostGloas(forkName)
382
383
  ? sszTypesFor(forkName).SignedBlockContents.fromJson(body)
383
- : {signedBlock: ssz[forkName].SignedBeaconBlock.fromJson(body)},
384
+ : ({signedBlock: sszTypesFor(forkName).SignedBeaconBlock.fromJson(body)} as SignedBlockContents<
385
+ ForkPreDeneb | ForkPostGloas
386
+ >),
384
387
  broadcastValidation: query.broadcast_validation as BroadcastValidation,
385
388
  builderUrl: fromHeaders(headers, MetaHeader.BuilderUrl, false),
386
389
  };
@@ -393,7 +396,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
393
396
  body:
394
397
  isForkPostDeneb(fork) && !isForkPostGloas(fork)
395
398
  ? sszTypesFor(fork).SignedBlockContents.serialize(
396
- signedBlockContents as SignedBlockContents<ForkPostDeneb>
399
+ signedBlockContents as SignedBlockContents<ForkPostDeneb & ForkPreGloas>
397
400
  )
398
401
  : sszTypesFor(fork).SignedBeaconBlock.serialize(
399
402
  signedBlockContents.signedBlock as SignedBeaconBlock<ForkPreDeneb | ForkPostGloas>
@@ -411,7 +414,9 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
411
414
  signedBlockContents:
412
415
  isForkPostDeneb(forkName) && !isForkPostGloas(forkName)
413
416
  ? sszTypesFor(forkName).SignedBlockContents.deserialize(body)
414
- : {signedBlock: ssz[forkName].SignedBeaconBlock.deserialize(body)},
417
+ : ({signedBlock: sszTypesFor(forkName).SignedBeaconBlock.deserialize(body)} as SignedBlockContents<
418
+ ForkPreDeneb | ForkPostGloas
419
+ >),
415
420
  broadcastValidation: query.broadcast_validation as BroadcastValidation,
416
421
  builderUrl: fromHeaders(headers, MetaHeader.BuilderUrl, false),
417
422
  };
@@ -1,6 +1,10 @@
1
+ import {ValueOf} from "@chainsafe/ssz";
1
2
  import {ChainForkConfig} from "@lodestar/config";
2
- import {phase0, ssz} from "@lodestar/types";
3
+ import {ArrayOf, Root, Slot, phase0, ssz} from "@lodestar/types";
4
+ import {fromHex, toRootHex} from "@lodestar/utils";
3
5
  import {EmptyArgs, EmptyMeta, EmptyMetaCodec, EmptyRequest, EmptyRequestCodec} from "../../../utils/codecs.js";
6
+ import {VersionCodec, VersionMeta} from "../../../utils/metadata.js";
7
+ import {Schema} from "../../../utils/schema.js";
4
8
  import {Endpoint, RouteDefinitions} from "../../../utils/types.js";
5
9
  import * as block from "./block.js";
6
10
  import * as pool from "./pool.js";
@@ -27,8 +31,13 @@ export type {
27
31
  ValidatorIdentities,
28
32
  ValidatorResponse,
29
33
  ValidatorStatus,
34
+ ValidatorStatusFilter,
30
35
  } from "./state.js";
31
36
 
37
+ const SignedProposerPreferencesListType = ArrayOf(ssz.gloas.SignedProposerPreferences);
38
+
39
+ type SignedProposerPreferencesList = ValueOf<typeof SignedProposerPreferencesListType>;
40
+
32
41
  export type Endpoints = block.Endpoints &
33
42
  pool.Endpoints &
34
43
  state.Endpoints &
@@ -41,6 +50,18 @@ export type Endpoints = block.Endpoints &
41
50
  phase0.Genesis,
42
51
  EmptyMeta
43
52
  >;
53
+
54
+ /**
55
+ * Get proposer preferences
56
+ * Retrieves the signed proposer preferences known by the node for upcoming proposal slots.
57
+ */
58
+ getProposerPreferences: Endpoint<
59
+ "GET",
60
+ {slot?: Slot; dependentRoot?: Root},
61
+ {query: {slot?: number; dependent_root?: string}},
62
+ SignedProposerPreferencesList,
63
+ VersionMeta
64
+ >;
44
65
  };
45
66
 
46
67
  export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
@@ -54,6 +75,24 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
54
75
  meta: EmptyMetaCodec,
55
76
  },
56
77
  },
78
+ getProposerPreferences: {
79
+ url: "/eth/v1/beacon/proposer_preferences",
80
+ method: "GET",
81
+ req: {
82
+ writeReq: ({slot, dependentRoot}) => ({
83
+ query: {slot, dependent_root: dependentRoot && toRootHex(dependentRoot)},
84
+ }),
85
+ parseReq: ({query}) => ({
86
+ slot: query.slot,
87
+ dependentRoot: query.dependent_root ? fromHex(query.dependent_root) : undefined,
88
+ }),
89
+ schema: {query: {slot: Schema.Uint, dependent_root: Schema.String}},
90
+ },
91
+ resp: {
92
+ data: SignedProposerPreferencesListType,
93
+ meta: VersionCodec,
94
+ },
95
+ },
57
96
  ...block.getDefinitions(config),
58
97
  ...pool.getDefinitions(config),
59
98
  ...state.getDefinitions(config),
@@ -6,6 +6,7 @@ import {
6
6
  BuilderStatus,
7
7
  CommitteeIndex,
8
8
  Epoch,
9
+ GeneralValidatorStatus,
9
10
  RootHex,
10
11
  Slot,
11
12
  StringType,
@@ -16,7 +17,8 @@ import {
16
17
  phase0,
17
18
  ssz,
18
19
  } from "@lodestar/types";
19
- import {JsonOnlyReq} from "../../../utils/codecs.js";
20
+ import {JsonOnlyReq, WithVersion} from "../../../utils/codecs.js";
21
+ import {getPostElectraForkTypes} from "../../../utils/fork.js";
20
22
  import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js";
21
23
  import {
22
24
  ExecutionOptimisticAndFinalizedCodec,
@@ -43,6 +45,12 @@ export type BuilderId = string | number;
43
45
 
44
46
  export type {BuilderStatus, ValidatorStatus};
45
47
 
48
+ /**
49
+ * Beacon API `status` filter: oneOf [ValidatorStatus, enum [active, pending, exited, withdrawal]].
50
+ * https://github.com/ethereum/beacon-APIs/blob/master/apis/beacon/states/validators.yaml
51
+ */
52
+ export type ValidatorStatusFilter = ValidatorStatus | GeneralValidatorStatus;
53
+
46
54
  export const RandaoResponseType = new ContainerType({
47
55
  randao: ssz.Root,
48
56
  });
@@ -91,6 +99,10 @@ export const EpochSyncCommitteeResponseType = new ContainerType(
91
99
  },
92
100
  {jsonCase: "eth2"}
93
101
  );
102
+ export const PtcResponseType = new ContainerType({
103
+ slot: ssz.Slot,
104
+ validators: ssz.gloas.PayloadTimelinessCommittee,
105
+ });
94
106
  export const ValidatorResponseListType = ArrayOf(ValidatorResponseType);
95
107
  export const BuilderResponseListType = ArrayOf(BuilderResponseType);
96
108
  export const ValidatorIdentitiesType = ArrayOf(ValidatorIdentityType);
@@ -104,6 +116,7 @@ export type BuilderResponse = ValueOf<typeof BuilderResponseType>;
104
116
  export type EpochCommitteeResponse = ValueOf<typeof EpochCommitteeResponseType>;
105
117
  export type ValidatorBalance = ValueOf<typeof ValidatorBalanceType>;
106
118
  export type EpochSyncCommitteeResponse = ValueOf<typeof EpochSyncCommitteeResponseType>;
119
+ export type PtcResponse = ValueOf<typeof PtcResponseType>;
107
120
 
108
121
  export type ValidatorResponseList = ValueOf<typeof ValidatorResponseListType>;
109
122
  export type BuilderResponseList = ValueOf<typeof BuilderResponseListType>;
@@ -191,9 +204,9 @@ export type Endpoints = {
191
204
  /** Either hex encoded public key (with 0x prefix) or validator index */
192
205
  validatorIds?: ValidatorId[];
193
206
  /** [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) */
194
- statuses?: ValidatorStatus[];
207
+ statuses?: ValidatorStatusFilter[];
195
208
  },
196
- {params: {state_id: string}; query: {id?: ValidatorId[]; status?: ValidatorStatus[]}},
209
+ {params: {state_id: string}; query: {id?: ValidatorId[]; status?: ValidatorStatusFilter[]}},
197
210
  ValidatorResponseList,
198
211
  ExecutionOptimisticAndFinalizedMeta
199
212
  >;
@@ -208,9 +221,9 @@ export type Endpoints = {
208
221
  /** Either hex encoded public key (with 0x prefix) or validator index */
209
222
  validatorIds?: ValidatorId[];
210
223
  /** [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) */
211
- statuses?: ValidatorStatus[];
224
+ statuses?: ValidatorStatusFilter[];
212
225
  },
213
- {params: {state_id: string}; body: {ids?: string[]; statuses?: ValidatorStatus[]}},
226
+ {params: {state_id: string}; body: {ids?: string[]; statuses?: ValidatorStatusFilter[]}},
214
227
  ValidatorResponseList,
215
228
  ExecutionOptimisticAndFinalizedMeta
216
229
  >;
@@ -316,6 +329,19 @@ export type Endpoints = {
316
329
  ExecutionOptimisticAndFinalizedMeta
317
330
  >;
318
331
 
332
+ /**
333
+ * Get the payload timeliness committee for a slot from the given state.
334
+ *
335
+ * Defaults to the state's slot. Validator indices are returned in committee order and may contain duplicates.
336
+ */
337
+ getStatePtc: Endpoint<
338
+ "GET",
339
+ StateArgs & {slot?: Slot},
340
+ {params: {state_id: string}; query: {slot?: number}},
341
+ PtcResponse,
342
+ ExecutionOptimisticAndFinalizedMeta
343
+ >;
344
+
319
345
  /**
320
346
  * Get State Pending Deposits
321
347
  *
@@ -444,6 +470,22 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
444
470
  meta: ExecutionOptimisticAndFinalizedCodec,
445
471
  },
446
472
  },
473
+ getStatePtc: {
474
+ url: "/eth/v1/beacon/states/{state_id}/ptc",
475
+ method: "GET",
476
+ req: {
477
+ writeReq: ({stateId, slot}) => ({params: {state_id: stateId.toString()}, query: {slot}}),
478
+ parseReq: ({params, query}) => ({stateId: params.state_id, slot: query.slot}),
479
+ schema: {
480
+ params: {state_id: Schema.StringRequired},
481
+ query: {slot: Schema.Uint},
482
+ },
483
+ },
484
+ resp: {
485
+ data: PtcResponseType,
486
+ meta: ExecutionOptimisticAndFinalizedCodec,
487
+ },
488
+ },
447
489
  getStateFinalityCheckpoints: {
448
490
  url: "/eth/v1/beacon/states/{state_id}/finality_checkpoints",
449
491
  method: "GET",
@@ -642,7 +684,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
642
684
  method: "GET",
643
685
  req: stateIdOnlyReq,
644
686
  resp: {
645
- data: ssz.electra.PendingDeposits,
687
+ data: WithVersion((fork) => getPostElectraForkTypes(fork).PendingDeposits),
646
688
  meta: ExecutionOptimisticFinalizedAndVersionCodec,
647
689
  },
648
690
  },
@@ -651,7 +693,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
651
693
  method: "GET",
652
694
  req: stateIdOnlyReq,
653
695
  resp: {
654
- data: ssz.electra.PendingPartialWithdrawals,
696
+ data: WithVersion((fork) => getPostElectraForkTypes(fork).PendingPartialWithdrawals),
655
697
  meta: ExecutionOptimisticFinalizedAndVersionCodec,
656
698
  },
657
699
  },
@@ -660,7 +702,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
660
702
  method: "GET",
661
703
  req: stateIdOnlyReq,
662
704
  resp: {
663
- data: ssz.electra.PendingConsolidations,
705
+ data: WithVersion((fork) => getPostElectraForkTypes(fork).PendingConsolidations),
664
706
  meta: ExecutionOptimisticFinalizedAndVersionCodec,
665
707
  },
666
708
  },
@@ -80,40 +80,52 @@ const ForkChoiceResponseType = new ContainerType(
80
80
 
81
81
  const ForkChoiceNodeV2ExtraDataType = new ContainerType(
82
82
  {
83
+ attestationScore: ssz.Gwei,
83
84
  executionOptimistic: ssz.Boolean,
85
+ gasLimit: new OptionalType(ssz.UintNum64),
84
86
  timestamp: ssz.UintNum64,
87
+ stateRoot: stringType,
85
88
  target: stringType,
86
- justifiedEpoch: ssz.Epoch,
87
- finalizedEpoch: ssz.Epoch,
88
89
  unrealizedJustifiedEpoch: ssz.Epoch,
90
+ unrealizedJustifiedRoot: stringType,
89
91
  unrealizedFinalizedEpoch: ssz.Epoch,
90
- payloadAttesterCount: new OptionalType(ssz.UintNum64),
91
- payloadAvailabilityYesCount: new OptionalType(ssz.UintNum64),
92
- payloadDataAvailabilityYesCount: new OptionalType(ssz.UintNum64),
93
- gasLimit: new OptionalType(ssz.UintNum64),
92
+ unrealizedFinalizedRoot: stringType,
94
93
  },
95
94
  {jsonCase: "eth2"}
96
95
  );
96
+ const payloadStatusType = new StringType<"pending" | "empty" | "full">();
97
97
  const ForkChoiceNodeV2Type = new ContainerType(
98
98
  {
99
- payloadStatus: new StringType<"pending" | "empty" | "full">(),
100
99
  slot: ssz.Slot,
101
100
  blockRoot: stringType,
101
+ payloadStatus: payloadStatusType,
102
102
  parentRoot: stringType,
103
+ parentPayloadStatus: new OptionalType(payloadStatusType),
104
+ justifiedCheckpoint: ssz.phase0.Checkpoint,
105
+ finalizedCheckpoint: ssz.phase0.Checkpoint,
103
106
  weight: ssz.Gwei,
104
107
  validity: new StringType<"valid" | "invalid" | "optimistic">(),
105
108
  executionBlockHash: stringType,
109
+ payloadAttesterCount: ssz.UintNum64,
110
+ payloadAvailabilityYesCount: ssz.UintNum64,
111
+ payloadDataAvailabilityYesCount: ssz.UintNum64,
106
112
  extraData: ForkChoiceNodeV2ExtraDataType,
107
113
  },
108
114
  {jsonCase: "eth2"}
109
115
  );
110
116
  const ForkChoiceExtraDataType = new ContainerType(
111
117
  {
112
- unrealizedJustifiedCheckpoint: ssz.phase0.Checkpoint,
113
- unrealizedFinalizedCheckpoint: ssz.phase0.Checkpoint,
118
+ head: new ContainerType(
119
+ {
120
+ blockRoot: stringType,
121
+ payloadStatus: payloadStatusType,
122
+ },
123
+ {jsonCase: "eth2"}
124
+ ),
114
125
  proposerBoostRoot: stringType,
115
126
  previousProposerBoostRoot: stringType,
116
- headRoot: stringType,
127
+ unrealizedJustifiedCheckpoint: ssz.phase0.Checkpoint,
128
+ unrealizedFinalizedCheckpoint: ssz.phase0.Checkpoint,
117
129
  },
118
130
  {jsonCase: "eth2"}
119
131
  );
@@ -255,14 +267,6 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
255
267
  data: ForkChoiceResponseV2Type,
256
268
  meta: EmptyMetaCodec,
257
269
  onlySupport: WireFormat.json,
258
- transform: {
259
- toResponse: (data) => ({
260
- ...(data as ForkChoiceResponseV2),
261
- }),
262
- fromResponse: (resp) => ({
263
- data: resp as ForkChoiceResponseV2,
264
- }),
265
- },
266
270
  },
267
271
  },
268
272
  getProtoArrayNodes: {
@@ -69,10 +69,31 @@ const headV2 = new ContainerType(
69
69
  },
70
70
  {typeName: "HeadV2", jsonCase: "eth2"}
71
71
  );
72
+ const phase0Block = new ContainerType(
73
+ {
74
+ slot: ssz.Slot,
75
+ block: stringType,
76
+ executionOptimistic: ssz.Boolean,
77
+ },
78
+ {typeName: "Block", jsonCase: "eth2"}
79
+ );
80
+ const gloasBlock = new ContainerType(
81
+ {
82
+ slot: ssz.Slot,
83
+ block: stringType,
84
+ blockHash: stringType,
85
+ builderIndex: ssz.BuilderIndex,
86
+ executionOptimistic: ssz.Boolean,
87
+ },
88
+ {typeName: "BlockGloas", jsonCase: "eth2"}
89
+ );
72
90
  type FuluDataColumnSidecarSSE = ValueOf<typeof fuluDataColumnSidecarSSE>;
73
91
  type GloasDataColumnSidecarSSE = ValueOf<typeof gloasDataColumnSidecarSSE>;
74
92
  type DataColumnSidecarSSE = FuluDataColumnSidecarSSE | GloasDataColumnSidecarSSE;
75
93
  type HeadV2 = ValueOf<typeof headV2>;
94
+ type Phase0BlockSSE = ValueOf<typeof phase0Block>;
95
+ type GloasBlockSSE = ValueOf<typeof gloasBlock>;
96
+ type BlockSSE = Phase0BlockSSE | GloasBlockSSE;
76
97
 
77
98
  export enum EventType {
78
99
  /**
@@ -182,11 +203,7 @@ export type EventData = {
182
203
  version: ForkName;
183
204
  data: HeadV2;
184
205
  };
185
- [EventType.block]: {
186
- slot: Slot;
187
- block: RootHex;
188
- executionOptimistic: boolean;
189
- };
206
+ [EventType.block]: BlockSSE;
190
207
  [EventType.blockGossip]: {
191
208
  slot: Slot;
192
209
  block: RootHex;
@@ -253,6 +270,8 @@ type EventstreamArgs = {
253
270
  topics: EventType[];
254
271
  signal: AbortSignal;
255
272
  onEvent: (event: BeaconEvent) => void;
273
+ /** Called every time the connection is established, including reconnects */
274
+ onOpen?: () => void;
256
275
  onError?: (err: Error) => void;
257
276
  onClose?: () => void;
258
277
  };
@@ -330,14 +349,22 @@ export function getTypeByEvent(config: ChainForkConfig): {[K in EventType]: Type
330
349
  ),
331
350
  [EventType.headV2]: WithVersion(() => headV2),
332
351
 
333
- [EventType.block]: new ContainerType(
334
- {
335
- slot: ssz.Slot,
336
- block: stringType,
337
- executionOptimistic: ssz.Boolean,
352
+ [EventType.block]: {
353
+ toJson: (data) => {
354
+ const fork = config.getForkName(data.slot);
355
+ if (isForkPostGloas(fork)) {
356
+ return gloasBlock.toJson(data as GloasBlockSSE);
357
+ }
358
+ return phase0Block.toJson(data);
338
359
  },
339
- {jsonCase: "eth2"}
340
- ),
360
+ fromJson: (data) => {
361
+ const fork = config.getForkName(Number((data as BlockSSE).slot));
362
+ if (isForkPostGloas(fork)) {
363
+ return gloasBlock.fromJson(data);
364
+ }
365
+ return phase0Block.fromJson(data);
366
+ },
367
+ },
341
368
  [EventType.blockGossip]: new ContainerType(
342
369
  {
343
370
  slot: ssz.Slot,
@@ -11,6 +11,7 @@ import {
11
11
  Slot,
12
12
  ValidatorIndex,
13
13
  ssz,
14
+ sszTypesFor,
14
15
  } from "@lodestar/types";
15
16
  import {
16
17
  EmptyArgs,
@@ -672,13 +673,13 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
672
673
  if (signedBlocks.length === 0) throw new Error("No blocks provided.");
673
674
  const fork = config.getForkName(signedBlocks[0].message.slot);
674
675
  return {
675
- body: ArrayOf(ssz[fork].SignedBeaconBlock).toJson(signedBlocks as SignedBeaconBlock<typeof fork>[]),
676
+ body: ArrayOf(sszTypesFor(fork).SignedBeaconBlock).toJson(signedBlocks),
676
677
  headers: {[MetaHeader.Version]: fork},
677
678
  };
678
679
  },
679
680
  parseReqJson: ({body, headers}) => {
680
681
  const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
681
- const signedBlocks = ArrayOf(ssz[fork].SignedBeaconBlock).fromJson(body) as SignedBeaconBlock[];
682
+ const signedBlocks = ArrayOf(sszTypesFor(fork).SignedBeaconBlock).fromJson(body);
682
683
  assertBlocksMatchFork(signedBlocks, fork);
683
684
  return {signedBlocks};
684
685
  },
@@ -686,13 +687,13 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
686
687
  if (signedBlocks.length === 0) throw new Error("No blocks provided.");
687
688
  const fork = config.getForkName(signedBlocks[0].message.slot);
688
689
  return {
689
- body: ArrayOf(ssz[fork].SignedBeaconBlock).serialize(signedBlocks as SignedBeaconBlock<typeof fork>[]),
690
+ body: ArrayOf(sszTypesFor(fork).SignedBeaconBlock).serialize(signedBlocks),
690
691
  headers: {[MetaHeader.Version]: fork},
691
692
  };
692
693
  },
693
694
  parseReqSsz: ({body, headers}) => {
694
695
  const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
695
- const signedBlocks = ArrayOf(ssz[fork].SignedBeaconBlock).deserialize(body) as SignedBeaconBlock[];
696
+ const signedBlocks = ArrayOf(sszTypesFor(fork).SignedBeaconBlock).deserialize(body);
696
697
  assertBlocksMatchFork(signedBlocks, fork);
697
698
  return {signedBlocks};
698
699
  },
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Re-exporting beacon only for backwards compatibility
2
2
  export * from "./beacon/index.js";
3
- export {ApiError} from "./utils/client/error.js";
3
+ export {ApiError, type ApiFailure} from "./utils/client/error.js";
4
4
  export type {
5
5
  ApiClientMethods,
6
6
  HttpClientModules,
@@ -1,10 +1,16 @@
1
+ /** Failure of a single item of a batch request, `index` refers to the submitted array */
2
+ export type ApiFailure = {index: number; message: string};
3
+
1
4
  export class ApiError extends Error {
2
5
  status: number;
3
6
  operationId: string;
7
+ /** Set if the server rejected only some items of a batch request */
8
+ failures?: ApiFailure[];
4
9
 
5
- constructor(message: string, status: number, operationId: string) {
10
+ constructor(message: string, status: number, operationId: string, failures?: ApiFailure[]) {
6
11
  super(`${operationId} failed with status ${status}: ${message}`);
7
12
  this.status = status;
8
13
  this.operationId = operationId;
14
+ this.failures = failures;
9
15
  }
10
16
  }
@@ -2,7 +2,7 @@ import {HeadersExtra, HttpHeader, parseContentTypeHeader} from "../headers.js";
2
2
  import {HttpStatusCode} from "../httpStatusCode.js";
3
3
  import {Endpoint} from "../types.js";
4
4
  import {WireFormat, getWireFormat} from "../wireFormat.js";
5
- import {ApiError} from "./error.js";
5
+ import {ApiError, ApiFailure} from "./error.js";
6
6
  import {RouteDefinitionExtra} from "./request.js";
7
7
 
8
8
  export type RawBody =
@@ -162,7 +162,8 @@ export class ApiResponse<E extends Endpoint> extends Response {
162
162
  return null;
163
163
  }
164
164
 
165
- return new ApiError(this.getErrorMessage(), this.status, this.definition.operationId);
165
+ const {message, failures} = this.parseErrorBody();
166
+ return new ApiError(message, this.status, this.definition.operationId, failures);
166
167
  }
167
168
 
168
169
  async errorBody(): Promise<string> {
@@ -187,19 +188,22 @@ export class ApiResponse<E extends Endpoint> extends Response {
187
188
  return this._errorBody;
188
189
  }
189
190
 
190
- private getErrorMessage(): string {
191
+ private parseErrorBody(): {message: string; failures?: ApiFailure[]} {
191
192
  const errBody = this.resolvedErrorBody();
192
193
  try {
193
- const errJson = JSON.parse(errBody) as {message?: string; failures?: {message: string}[]};
194
+ const errJson = JSON.parse(errBody) as {message?: string; failures?: ApiFailure[]};
194
195
  if (errJson.message) {
195
- if (errJson.failures) {
196
- return `${errJson.message}\n` + errJson.failures.map((e) => e.message).join("\n");
196
+ if (Array.isArray(errJson.failures)) {
197
+ return {
198
+ message: `${errJson.message}\n` + errJson.failures.map((e) => e.message).join("\n"),
199
+ failures: errJson.failures,
200
+ };
197
201
  }
198
- return errJson.message;
202
+ return {message: errJson.message};
199
203
  }
200
- return errBody;
204
+ return {message: errBody};
201
205
  } catch (_e) {
202
- return errBody || this.statusText;
206
+ return {message: errBody || this.statusText};
203
207
  }
204
208
  }
205
209
  }
package/src/utils/fork.ts CHANGED
@@ -3,10 +3,12 @@ import {
3
3
  ForkPostAltair,
4
4
  ForkPostBellatrix,
5
5
  ForkPostDeneb,
6
+ ForkPostElectra,
6
7
  ForkPostGloas,
7
8
  isForkPostAltair,
8
9
  isForkPostBellatrix,
9
10
  isForkPostDeneb,
11
+ isForkPostElectra,
10
12
  isForkPostGloas,
11
13
  } from "@lodestar/params";
12
14
  import {SSZTypesFor, sszTypesFor} from "@lodestar/types";
@@ -45,6 +47,14 @@ export function getPostDenebForkTypes(fork: ForkName): SSZTypesFor<ForkPostDeneb
45
47
  return sszTypesFor(fork);
46
48
  }
47
49
 
50
+ export function getPostElectraForkTypes(fork: ForkName): SSZTypesFor<ForkPostElectra> {
51
+ if (!isForkPostElectra(fork)) {
52
+ throw Error(`Invalid fork=${fork} for post-electra fork types`);
53
+ }
54
+
55
+ return sszTypesFor(fork);
56
+ }
57
+
48
58
  export function getPostGloasForkTypes(fork: ForkName): SSZTypesFor<ForkPostGloas> {
49
59
  if (!isForkPostGloas(fork)) {
50
60
  throw Error(`Invalid fork=${fork} for post-gloas fork types`);