@lodestar/api 1.47.0-dev.d2f4b96574 → 1.47.0-dev.d477ac6ef7
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.
- package/lib/beacon/routes/beacon/block.d.ts +7 -0
- package/lib/beacon/routes/beacon/block.d.ts.map +1 -1
- package/lib/beacon/routes/beacon/block.js +7 -3
- package/lib/beacon/routes/beacon/block.js.map +1 -1
- package/lib/beacon/routes/beacon/pool.d.ts +2 -12
- package/lib/beacon/routes/beacon/pool.d.ts.map +1 -1
- package/lib/beacon/routes/beacon/pool.js +2 -3
- package/lib/beacon/routes/beacon/pool.js.map +1 -1
- package/lib/beacon/routes/debug.d.ts +3 -3
- package/lib/beacon/routes/debug.js +3 -3
- package/lib/beacon/routes/debug.js.map +1 -1
- package/lib/beacon/routes/lodestar.d.ts +33 -18
- package/lib/beacon/routes/lodestar.d.ts.map +1 -1
- package/lib/beacon/routes/lodestar.js +18 -2
- package/lib/beacon/routes/lodestar.js.map +1 -1
- package/lib/beacon/routes/validator.d.ts +98 -7
- package/lib/beacon/routes/validator.d.ts.map +1 -1
- package/lib/beacon/routes/validator.js +125 -23
- package/lib/beacon/routes/validator.js.map +1 -1
- package/lib/beacon/server/events.d.ts.map +1 -1
- package/lib/beacon/server/events.js +10 -2
- package/lib/beacon/server/events.js.map +1 -1
- package/lib/builder/routes.d.ts +48 -1
- package/lib/builder/routes.d.ts.map +1 -1
- package/lib/builder/routes.js +173 -1
- package/lib/builder/routes.js.map +1 -1
- package/lib/keymanager/index.d.ts +2 -2
- package/lib/keymanager/index.d.ts.map +1 -1
- package/lib/keymanager/index.js +1 -1
- package/lib/keymanager/index.js.map +1 -1
- package/lib/keymanager/routes.d.ts +63 -0
- package/lib/keymanager/routes.d.ts.map +1 -1
- package/lib/keymanager/routes.js +147 -0
- package/lib/keymanager/routes.js.map +1 -1
- package/lib/utils/client/httpClient.js +2 -2
- package/lib/utils/client/httpClient.js.map +1 -1
- package/lib/utils/client/request.d.ts +4 -1
- package/lib/utils/client/request.d.ts.map +1 -1
- package/lib/utils/client/request.js +11 -6
- package/lib/utils/client/request.js.map +1 -1
- package/lib/utils/client/response.js +1 -1
- package/lib/utils/client/response.js.map +1 -1
- package/lib/utils/metadata.d.ts +3 -0
- package/lib/utils/metadata.d.ts.map +1 -1
- package/lib/utils/metadata.js +4 -0
- package/lib/utils/metadata.js.map +1 -1
- package/lib/utils/types.d.ts +1 -1
- package/lib/utils/types.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/beacon/routes/beacon/block.ts +18 -4
- package/src/beacon/routes/beacon/pool.ts +3 -11
- package/src/beacon/routes/debug.ts +3 -3
- package/src/beacon/routes/lodestar.ts +24 -19
- package/src/beacon/routes/validator.ts +189 -30
- package/src/beacon/server/events.ts +10 -3
- package/src/builder/routes.ts +227 -2
- package/src/keymanager/index.ts +9 -1
- package/src/keymanager/routes.ts +227 -0
- package/src/utils/client/httpClient.ts +2 -2
- package/src/utils/client/request.ts +12 -7
- package/src/utils/client/response.ts +1 -1
- package/src/utils/metadata.ts +5 -0
- 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.
|
|
94
|
+
executionPayloadValue: ssz.Wei,
|
|
86
95
|
/** Consensus rewards paid to the proposer for this block, in Wei */
|
|
87
|
-
consensusBlockValue: ssz.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
|
|
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: "
|
|
998
|
+
method: "POST",
|
|
914
999
|
req: {
|
|
915
|
-
|
|
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
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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) =>
|
|
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
|
},
|
package/src/builder/routes.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/keymanager/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
|