@lodestar/api 1.47.0-dev.90204c280d → 1.47.0-dev.92b9ec6e8b
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/validator.d.ts +92 -5
- package/lib/beacon/routes/validator.d.ts.map +1 -1
- package/lib/beacon/routes/validator.js +125 -20
- 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/validator.ts +182 -24
- 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,
|
|
@@ -88,9 +91,9 @@ export const ProduceBlockV3MetaType = new ContainerType(
|
|
|
88
91
|
/** Specifies whether the response contains full or blinded block */
|
|
89
92
|
executionPayloadBlinded: ssz.Boolean,
|
|
90
93
|
/** Execution payload value in Wei */
|
|
91
|
-
executionPayloadValue: ssz.
|
|
94
|
+
executionPayloadValue: ssz.Wei,
|
|
92
95
|
/** Consensus rewards paid to the proposer for this block, in Wei */
|
|
93
|
-
consensusBlockValue: ssz.
|
|
96
|
+
consensusBlockValue: ssz.Wei,
|
|
94
97
|
},
|
|
95
98
|
{jsonCase: "eth2"}
|
|
96
99
|
);
|
|
@@ -104,16 +107,19 @@ export const ProduceBlockV4MetaType = new ContainerType(
|
|
|
104
107
|
{
|
|
105
108
|
...VersionType.fields,
|
|
106
109
|
/** Consensus rewards paid to the proposer for this block, in Wei */
|
|
107
|
-
consensusBlockValue: ssz.
|
|
110
|
+
consensusBlockValue: ssz.Wei,
|
|
108
111
|
/** Local execution payload value when self-building, or builder bid value when committing to a bid, in Wei */
|
|
109
|
-
executionPayloadValue: ssz.
|
|
112
|
+
executionPayloadValue: ssz.Wei,
|
|
110
113
|
/** Specifies whether the response contains full block contents or only the beacon block */
|
|
111
114
|
executionPayloadIncluded: ssz.Boolean,
|
|
112
115
|
},
|
|
113
116
|
{jsonCase: "eth2"}
|
|
114
117
|
);
|
|
115
118
|
|
|
116
|
-
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
|
+
};
|
|
117
123
|
|
|
118
124
|
export const AttesterDutyType = new ContainerType(
|
|
119
125
|
{
|
|
@@ -264,6 +270,59 @@ export const SignedProposerPreferencesListType = ArrayOf(
|
|
|
264
270
|
(MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
|
|
265
271
|
);
|
|
266
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
|
+
|
|
267
326
|
export type ValidatorIndices = ValueOf<typeof ValidatorIndicesType>;
|
|
268
327
|
export type AttesterDuty = ValueOf<typeof AttesterDutyType>;
|
|
269
328
|
export type AttesterDutyList = ValueOf<typeof AttesterDutyListType>;
|
|
@@ -291,6 +350,10 @@ export type LivenessResponseData = ValueOf<typeof LivenessResponseDataType>;
|
|
|
291
350
|
export type LivenessResponseDataList = ValueOf<typeof LivenessResponseDataListType>;
|
|
292
351
|
export type SignedValidatorRegistrationV1List = ValueOf<typeof SignedValidatorRegistrationV1ListType>;
|
|
293
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>;
|
|
294
357
|
|
|
295
358
|
// The beacon node does not return any data if there is no canonical block at the requested slot (missed slot).
|
|
296
359
|
// In this case, we receive a success response (204) which is not handled as an error. The generic response
|
|
@@ -433,6 +496,9 @@ export type Endpoints = {
|
|
|
433
496
|
* so there is no longer a concept of blinded or unblinded blocks. Builders release the payload later.
|
|
434
497
|
* This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks.
|
|
435
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
|
+
*
|
|
436
502
|
* When self-building and `includePayload` is true, the response contains the full `BlockContents`
|
|
437
503
|
* (block, execution payload envelope, KZG proofs and blobs) which enables stateless envelope
|
|
438
504
|
* publishing via any beacon node. When `includePayload` is false, only the `BeaconBlock` is
|
|
@@ -440,7 +506,7 @@ export type Endpoints = {
|
|
|
440
506
|
* When committing to a builder bid, only the `BeaconBlock` is returned in either case.
|
|
441
507
|
*/
|
|
442
508
|
produceBlockV4: Endpoint<
|
|
443
|
-
"
|
|
509
|
+
"POST",
|
|
444
510
|
{
|
|
445
511
|
/** The slot for which the block should be proposed */
|
|
446
512
|
slot: Slot;
|
|
@@ -449,9 +515,9 @@ export type Endpoints = {
|
|
|
449
515
|
/** Arbitrary data validator wants to include in block */
|
|
450
516
|
graffiti?: string;
|
|
451
517
|
skipRandaoVerification?: boolean;
|
|
452
|
-
builderBoostFactor?: UintBn64;
|
|
453
518
|
/** Include execution payload envelope and blobs in the response when self-building */
|
|
454
519
|
includePayload: boolean;
|
|
520
|
+
builderConfig: BuilderConfig;
|
|
455
521
|
} & ExtraProduceBlockV4Opts,
|
|
456
522
|
{
|
|
457
523
|
params: {slot: number};
|
|
@@ -460,10 +526,11 @@ export type Endpoints = {
|
|
|
460
526
|
graffiti?: string;
|
|
461
527
|
skip_randao_verification?: string;
|
|
462
528
|
fee_recipient?: string;
|
|
463
|
-
builder_boost_factor?: string;
|
|
464
529
|
strict_fee_recipient_check?: boolean;
|
|
465
530
|
include_payload: boolean;
|
|
466
531
|
};
|
|
532
|
+
body: unknown;
|
|
533
|
+
headers: {[MetaHeader.Version]: string};
|
|
467
534
|
},
|
|
468
535
|
BeaconBlock<ForkPostGloas> | BlockContents<ForkPostGloas>,
|
|
469
536
|
ProduceBlockV4Meta
|
|
@@ -693,6 +760,19 @@ export type Endpoints = {
|
|
|
693
760
|
EmptyResponseData,
|
|
694
761
|
EmptyMeta
|
|
695
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
|
+
>;
|
|
696
776
|
};
|
|
697
777
|
|
|
698
778
|
export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
|
|
@@ -915,17 +995,17 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
|
|
|
915
995
|
},
|
|
916
996
|
produceBlockV4: {
|
|
917
997
|
url: "/eth/v4/validator/blocks/{slot}",
|
|
918
|
-
method: "
|
|
998
|
+
method: "POST",
|
|
919
999
|
req: {
|
|
920
|
-
|
|
1000
|
+
writeReqJson: ({
|
|
921
1001
|
slot,
|
|
922
1002
|
randaoReveal,
|
|
923
1003
|
graffiti,
|
|
924
1004
|
skipRandaoVerification,
|
|
925
1005
|
feeRecipient,
|
|
926
|
-
builderBoostFactor,
|
|
927
1006
|
strictFeeRecipientCheck,
|
|
928
1007
|
includePayload,
|
|
1008
|
+
builderConfig,
|
|
929
1009
|
}) => ({
|
|
930
1010
|
params: {slot},
|
|
931
1011
|
query: {
|
|
@@ -933,21 +1013,60 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
|
|
|
933
1013
|
graffiti: toGraffitiHex(graffiti),
|
|
934
1014
|
skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification),
|
|
935
1015
|
fee_recipient: feeRecipient,
|
|
936
|
-
builder_boost_factor: builderBoostFactor?.toString(),
|
|
937
1016
|
strict_fee_recipient_check: strictFeeRecipientCheck,
|
|
938
1017
|
include_payload: includePayload,
|
|
939
1018
|
},
|
|
1019
|
+
body: BuilderConfigType.toJson(builderConfig),
|
|
1020
|
+
headers: {[MetaHeader.Version]: config.getForkName(slot)},
|
|
940
1021
|
}),
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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)},
|
|
950
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
|
+
},
|
|
951
1070
|
schema: {
|
|
952
1071
|
params: {slot: Schema.UintRequired},
|
|
953
1072
|
query: {
|
|
@@ -955,12 +1074,16 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
|
|
|
955
1074
|
graffiti: Schema.String,
|
|
956
1075
|
skip_randao_verification: Schema.String,
|
|
957
1076
|
fee_recipient: Schema.String,
|
|
958
|
-
builder_boost_factor: Schema.String,
|
|
959
1077
|
strict_fee_recipient_check: Schema.Boolean,
|
|
960
1078
|
include_payload: Schema.BooleanRequired,
|
|
961
1079
|
},
|
|
1080
|
+
body: Schema.Object,
|
|
1081
|
+
headers: {[MetaHeader.Version]: Schema.String},
|
|
962
1082
|
},
|
|
963
1083
|
},
|
|
1084
|
+
init: {
|
|
1085
|
+
requestWireFormat: WireFormat.ssz,
|
|
1086
|
+
},
|
|
964
1087
|
resp: {
|
|
965
1088
|
data: WithMeta(
|
|
966
1089
|
({version, executionPayloadIncluded}) =>
|
|
@@ -972,18 +1095,23 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
|
|
|
972
1095
|
),
|
|
973
1096
|
meta: {
|
|
974
1097
|
toJson: (meta) => ProduceBlockV4MetaType.toJson(meta),
|
|
975
|
-
fromJson: (val) =>
|
|
1098
|
+
fromJson: (val, headers) => ({
|
|
1099
|
+
...ProduceBlockV4MetaType.fromJson(val),
|
|
1100
|
+
builderUrl: headers?.get(MetaHeader.BuilderUrl) ?? undefined,
|
|
1101
|
+
}),
|
|
976
1102
|
toHeadersObject: (meta) => ({
|
|
977
1103
|
[MetaHeader.Version]: meta.version,
|
|
978
1104
|
[MetaHeader.ConsensusBlockValue]: meta.consensusBlockValue.toString(),
|
|
979
1105
|
[MetaHeader.ExecutionPayloadValue]: meta.executionPayloadValue.toString(),
|
|
980
1106
|
[MetaHeader.ExecutionPayloadIncluded]: meta.executionPayloadIncluded.toString(),
|
|
1107
|
+
...(meta.builderUrl !== undefined ? {[MetaHeader.BuilderUrl]: meta.builderUrl} : {}),
|
|
981
1108
|
}),
|
|
982
1109
|
fromHeaders: (headers) => ({
|
|
983
1110
|
version: toForkName(headers.getRequired(MetaHeader.Version)),
|
|
984
1111
|
consensusBlockValue: BigInt(headers.getRequired(MetaHeader.ConsensusBlockValue)),
|
|
985
1112
|
executionPayloadValue: BigInt(headers.getRequired(MetaHeader.ExecutionPayloadValue)),
|
|
986
1113
|
executionPayloadIncluded: toBoolean(headers.getRequired(MetaHeader.ExecutionPayloadIncluded)),
|
|
1114
|
+
builderUrl: headers.get(MetaHeader.BuilderUrl) ?? undefined,
|
|
987
1115
|
}),
|
|
988
1116
|
},
|
|
989
1117
|
},
|
|
@@ -1295,6 +1423,36 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
|
|
|
1295
1423
|
},
|
|
1296
1424
|
resp: EmptyResponseCodec,
|
|
1297
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
|
+
},
|
|
1298
1456
|
};
|
|
1299
1457
|
}
|
|
1300
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
|
|