UNPKG

@lodestar/api

Version:

A Typescript REST client for the Ethereum Consensus API

1,220 lines (1,173 loc) • 46.4 kB
import {ContainerType, Type, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import { ForkPostDeneb, ForkPostGloas, ForkPreDeneb, VALIDATOR_REGISTRY_LIMIT, isForkPostDeneb, isForkPostElectra, } from "@lodestar/params"; import { ArrayOf, Attestation, BLSSignature, BeaconBlock, BlindedBeaconBlock, BlockContents, CommitteeIndex, Epoch, ProducedBlockSource, Root, Slot, UintBn64, ValidatorIndex, altair, gloas, phase0, ssz, sszTypesFor, stringType, } from "@lodestar/types"; import {fromHex, toHex, toRootHex} from "@lodestar/utils"; import { EmptyMeta, EmptyMetaCodec, EmptyResponseCodec, EmptyResponseData, JsonOnlyReq, WithVersion, } from "../../utils/codecs.js"; import {getPostBellatrixForkTypes, getPostGloasForkTypes, toForkName} from "../../utils/fork.js"; import {fromHeaders} from "../../utils/headers.js"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; import { ExecutionOptimisticAndDependentRootCodec, ExecutionOptimisticAndDependentRootMeta, ExecutionOptimisticCodec, ExecutionOptimisticMeta, MetaHeader, VersionCodec, VersionMeta, VersionType, } from "../../utils/metadata.js"; import {fromGraffitiHex, toBoolean, toGraffitiHex} from "../../utils/serdes.js"; import {WireFormat} from "../../utils/wireFormat.js"; export enum BuilderSelection { Default = "default", BuilderAlways = "builderalways", ExecutionAlways = "executionalways", MaxProfit = "maxprofit", /** Only activate builder flow for DVT block proposal protocols */ BuilderOnly = "builderonly", /** Only builds execution block*/ ExecutionOnly = "executiononly", } /** Lodestar-specific (non-standardized) options */ export type ExtraProduceBlockOpts = { feeRecipient?: string; builderSelection?: BuilderSelection; strictFeeRecipientCheck?: boolean; blindedLocal?: boolean; }; export const ProduceBlockV3MetaType = new ContainerType( { ...VersionType.fields, /** Specifies whether the response contains full or blinded block */ executionPayloadBlinded: ssz.Boolean, /** Execution payload value in Wei */ executionPayloadValue: ssz.UintBn64, /** Consensus rewards paid to the proposer for this block, in Wei */ consensusBlockValue: ssz.UintBn64, }, {jsonCase: "eth2"} ); export type ProduceBlockV3Meta = ValueOf<typeof ProduceBlockV3MetaType> & { /** Lodestar-specific (non-standardized) value */ executionPayloadSource: ProducedBlockSource; }; export const ProduceBlockV4MetaType = new ContainerType( { ...VersionType.fields, /** Consensus rewards paid to the proposer for this block, in Wei */ consensusBlockValue: ssz.UintBn64, }, {jsonCase: "eth2"} ); export type ProduceBlockV4Meta = ValueOf<typeof ProduceBlockV4MetaType>; export const AttesterDutyType = new ContainerType( { /** The validator's public key, uniquely identifying them */ pubkey: ssz.BLSPubkey, /** Index of validator in validator registry */ validatorIndex: ssz.ValidatorIndex, /** Index of the committee */ committeeIndex: ssz.CommitteeIndex, /** Number of validators in committee */ committeeLength: ssz.UintNum64, /** Number of committees at the provided slot */ committeesAtSlot: ssz.UintNum64, /** Index of validator in committee */ validatorCommitteeIndex: ssz.UintNum64, /** The slot at which the validator must attest */ slot: ssz.Slot, }, {jsonCase: "eth2"} ); export const ProposerDutyType = new ContainerType( { slot: ssz.Slot, validatorIndex: ssz.ValidatorIndex, pubkey: ssz.BLSPubkey, }, {jsonCase: "eth2"} ); /** * From https://github.com/ethereum/beacon-APIs/pull/134 */ export const SyncDutyType = new ContainerType( { pubkey: ssz.BLSPubkey, /** Index of validator in validator registry. */ validatorIndex: ssz.ValidatorIndex, /** The indices of the validator in the sync committee. */ validatorSyncCommitteeIndices: ArrayOf(ssz.CommitteeIndex), }, {jsonCase: "eth2"} ); export const BeaconCommitteeSubscriptionType = new ContainerType( { validatorIndex: ssz.ValidatorIndex, committeeIndex: ssz.CommitteeIndex, committeesAtSlot: ssz.Slot, slot: ssz.Slot, isAggregator: ssz.Boolean, }, {jsonCase: "eth2"} ); /** * From https://github.com/ethereum/beacon-APIs/pull/136 */ export const SyncCommitteeSubscriptionType = new ContainerType( { validatorIndex: ssz.ValidatorIndex, syncCommitteeIndices: ArrayOf(ssz.CommitteeIndex), untilEpoch: ssz.Epoch, }, {jsonCase: "eth2"} ); export const ProposerPreparationDataType = new ContainerType( { validatorIndex: ssz.ValidatorIndex, feeRecipient: stringType, }, {jsonCase: "eth2"} ); /** * From https://github.com/ethereum/beacon-APIs/pull/224 */ export const BeaconCommitteeSelectionType = new ContainerType( { /** Index of the validator */ validatorIndex: ssz.ValidatorIndex, /** The slot at which a validator is assigned to attest */ slot: ssz.Slot, /** The `slot_signature` calculated by the validator for the upcoming attestation slot */ selectionProof: ssz.BLSSignature, }, {jsonCase: "eth2"} ); /** * From https://github.com/ethereum/beacon-APIs/pull/224 */ export const SyncCommitteeSelectionType = new ContainerType( { /** Index of the validator */ validatorIndex: ssz.ValidatorIndex, /** The slot at which validator is assigned to produce a sync committee contribution */ slot: ssz.Slot, /** SubcommitteeIndex to which the validator is assigned */ subcommitteeIndex: ssz.SubcommitteeIndex, /** The `slot_signature` calculated by the validator for the upcoming sync committee slot */ selectionProof: ssz.BLSSignature, }, {jsonCase: "eth2"} ); export const LivenessResponseDataType = new ContainerType( { index: ssz.ValidatorIndex, isLive: ssz.Boolean, }, {jsonCase: "eth2"} ); export const ValidatorIndicesType = ArrayOf(ssz.ValidatorIndex); export const AttesterDutyListType = ArrayOf(AttesterDutyType); export const ProposerDutyListType = ArrayOf(ProposerDutyType); export const SyncDutyListType = ArrayOf(SyncDutyType); export const SignedAggregateAndProofListPhase0Type = ArrayOf(ssz.phase0.SignedAggregateAndProof); export const SignedAggregateAndProofListElectraType = ArrayOf(ssz.electra.SignedAggregateAndProof); export const SignedContributionAndProofListType = ArrayOf(ssz.altair.SignedContributionAndProof); export const BeaconCommitteeSubscriptionListType = ArrayOf(BeaconCommitteeSubscriptionType); export const SyncCommitteeSubscriptionListType = ArrayOf(SyncCommitteeSubscriptionType); export const ProposerPreparationDataListType = ArrayOf(ProposerPreparationDataType); export const BeaconCommitteeSelectionListType = ArrayOf(BeaconCommitteeSelectionType); export const SyncCommitteeSelectionListType = ArrayOf(SyncCommitteeSelectionType); export const LivenessResponseDataListType = ArrayOf(LivenessResponseDataType); export const SignedValidatorRegistrationV1ListType = ArrayOf( ssz.bellatrix.SignedValidatorRegistrationV1, VALIDATOR_REGISTRY_LIMIT ); export type ValidatorIndices = ValueOf<typeof ValidatorIndicesType>; export type AttesterDuty = ValueOf<typeof AttesterDutyType>; export type AttesterDutyList = ValueOf<typeof AttesterDutyListType>; export type ProposerDuty = ValueOf<typeof ProposerDutyType>; export type ProposerDutyList = ValueOf<typeof ProposerDutyListType>; export type SyncDuty = ValueOf<typeof SyncDutyType>; export type SyncDutyList = ValueOf<typeof SyncDutyListType>; export type SignedAggregateAndProofListPhase0 = ValueOf<typeof SignedAggregateAndProofListPhase0Type>; export type SignedAggregateAndProofListElectra = ValueOf<typeof SignedAggregateAndProofListElectraType>; export type SignedAggregateAndProofList = SignedAggregateAndProofListPhase0 | SignedAggregateAndProofListElectra; export type SignedContributionAndProofList = ValueOf<typeof SignedContributionAndProofListType>; export type BeaconCommitteeSubscription = ValueOf<typeof BeaconCommitteeSubscriptionType>; export type BeaconCommitteeSubscriptionList = ValueOf<typeof BeaconCommitteeSubscriptionListType>; export type SyncCommitteeSubscription = ValueOf<typeof SyncCommitteeSubscriptionType>; export type SyncCommitteeSubscriptionList = ValueOf<typeof SyncCommitteeSubscriptionListType>; export type ProposerPreparationData = ValueOf<typeof ProposerPreparationDataType>; export type ProposerPreparationDataList = ValueOf<typeof ProposerPreparationDataListType>; export type BeaconCommitteeSelection = ValueOf<typeof BeaconCommitteeSelectionType>; export type BeaconCommitteeSelectionList = ValueOf<typeof BeaconCommitteeSelectionListType>; export type SyncCommitteeSelection = ValueOf<typeof SyncCommitteeSelectionType>; export type SyncCommitteeSelectionList = ValueOf<typeof SyncCommitteeSelectionListType>; export type LivenessResponseData = ValueOf<typeof LivenessResponseDataType>; export type LivenessResponseDataList = ValueOf<typeof LivenessResponseDataListType>; export type SignedValidatorRegistrationV1List = ValueOf<typeof SignedValidatorRegistrationV1ListType>; export type Endpoints = { /** * Get attester duties * Requests the beacon node to provide a set of attestation duties, which should be performed by validators, for a particular epoch. * Duties should only need to be checked once per epoch, however a chain reorganization (of > MIN_SEED_LOOKAHEAD epochs) could occur, resulting in a change of duties. For full safety, you should monitor head events and confirm the dependent root in this response matches: * - event.previous_duty_dependent_root when `compute_epoch_at_slot(event.slot) == epoch` * - event.current_duty_dependent_root when `compute_epoch_at_slot(event.slot) + 1 == epoch` * - event.block otherwise * The dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1)` or the genesis block root in the case of underflow. */ getAttesterDuties: Endpoint< "POST", { /** Should only be allowed 1 epoch ahead */ epoch: Epoch; /** An array of the validator indices for which to obtain the duties */ indices: ValidatorIndices; }, {params: {epoch: Epoch}; body: unknown}, AttesterDutyList, ExecutionOptimisticAndDependentRootMeta >; /** * Get block proposers duties * Request beacon node to provide all validators that are scheduled to propose a block in the given epoch. * Duties should only need to be checked once per epoch, however a chain reorganization could occur that results in a change of duties. For full safety, you should monitor head events and confirm the dependent root in this response matches: * - event.current_duty_dependent_root when `compute_epoch_at_slot(event.slot) == epoch` * - event.block otherwise * The dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1)` or the genesis block root in the case of underflow. */ getProposerDuties: Endpoint< "GET", {epoch: Epoch}, {params: {epoch: Epoch}}, ProposerDutyList, ExecutionOptimisticAndDependentRootMeta >; /** * Get block proposers duties * Request beacon node to provide all validators that are scheduled to propose a block in the given epoch. * Duties should only need to be checked once per epoch, however a chain reorganization could occur that results in a change of duties. * For full safety, you should monitor head events and confirm the dependent root in this response matches. After Fulu, different checks * need to be performed as the dependent root changes due to deterministic proposer lookahead. * * Before Fulu: * - event.current_duty_dependent_root when `compute_epoch_at_slot(event.slot) == epoch` * - event.block otherwise * - dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1)` * * After Fulu: * - event.previous_duty_dependent_root when `compute_epoch_at_slot(event.slot) == epoch` * - event.block otherwise * - dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1)` * * The dependent_root value is the genesis block root in the case of underflow." */ getProposerDutiesV2: Endpoint< "GET", {epoch: Epoch}, {params: {epoch: Epoch}}, ProposerDutyList, ExecutionOptimisticAndDependentRootMeta >; getSyncCommitteeDuties: Endpoint< "POST", { epoch: number; indices: ValidatorIndices; }, {params: {epoch: Epoch}; body: unknown}, SyncDutyList, ExecutionOptimisticMeta >; /** * Requests a beacon node to produce a valid block, which can then be signed by a validator. * Metadata in the response indicates the type of block produced, and the supported types of block * will be added to as forks progress. */ produceBlockV3: Endpoint< "GET", { /** The slot for which the block should be proposed */ slot: Slot; /** The validator's randao reveal value */ randaoReveal: BLSSignature; /** Arbitrary data validator wants to include in block */ graffiti?: string; skipRandaoVerification?: boolean; builderBoostFactor?: UintBn64; } & ExtraProduceBlockOpts, { params: {slot: number}; query: { randao_reveal: string; graffiti?: string; skip_randao_verification?: string; fee_recipient?: string; builder_selection?: string; builder_boost_factor?: string; strict_fee_recipient_check?: boolean; blinded_local?: boolean; }; }, BlockContents | BlindedBeaconBlock, ProduceBlockV3Meta >; /** * Requests a beacon node to produce a valid block, which can then be signed by a validator. * * Post-Gloas, proposers submit execution payload bids rather than full execution payloads, * so there is no longer a concept of blinded or unblinded blocks. Builders release the payload later. * This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks. */ produceBlockV4: Endpoint< "GET", { /** The slot for which the block should be proposed */ slot: Slot; /** The validator's randao reveal value */ randaoReveal: BLSSignature; /** Arbitrary data validator wants to include in block */ graffiti?: string; skipRandaoVerification?: boolean; builderBoostFactor?: UintBn64; } & Omit<ExtraProduceBlockOpts, "blindedLocal">, { params: {slot: number}; query: { randao_reveal: string; graffiti?: string; skip_randao_verification?: string; fee_recipient?: string; builder_selection?: string; builder_boost_factor?: string; strict_fee_recipient_check?: boolean; }; }, BeaconBlock<ForkPostGloas>, ProduceBlockV4Meta >; /** * Get execution payload envelope. * Retrieves execution payload envelope for a given slot and beacon block root. * The envelope contains the full execution payload along with associated metadata. */ getExecutionPayloadEnvelope: Endpoint< "GET", { /** Slot for which the execution payload envelope is requested */ slot: Slot; /** Root of the beacon block that this envelope is for */ beaconBlockRoot: Root; }, {params: {slot: Slot; beacon_block_root: string}}, gloas.ExecutionPayloadEnvelope, VersionMeta >; /** * Produce an attestation data * Requests that the beacon node produce an AttestationData. */ produceAttestationData: Endpoint< "GET", { /** The committee index for which an attestation data should be created */ committeeIndex?: CommitteeIndex; /** The slot for which an attestation data should be created */ slot: Slot; }, {query: {slot: number; committee_index?: number}}, phase0.AttestationData, EmptyMeta >; produceSyncCommitteeContribution: Endpoint< "GET", { slot: Slot; subcommitteeIndex: number; beaconBlockRoot: Root; }, {query: {slot: number; subcommittee_index: number; beacon_block_root: string}}, altair.SyncCommitteeContribution, EmptyMeta >; /** * Get aggregated attestation * Aggregates all attestations matching given attestation data root and slot * Returns an aggregated `Attestation` object with same `AttestationData` root. */ getAggregatedAttestation: Endpoint< "GET", { /** HashTreeRoot of AttestationData that validator want's aggregated */ attestationDataRoot: Root; slot: Slot; }, {query: {attestation_data_root: string; slot: number}}, phase0.Attestation, EmptyMeta >; /** * Get aggregated attestation * Aggregates all attestations matching given attestation data root, slot and committee index * Returns an aggregated `Attestation` object with same `AttestationData` root. */ getAggregatedAttestationV2: Endpoint< "GET", { /** HashTreeRoot of AttestationData that validator want's aggregated */ attestationDataRoot: Root; slot: Slot; committeeIndex: number; }, {query: {attestation_data_root: string; slot: number; committee_index: number}}, Attestation, VersionMeta >; /** * Publish multiple aggregate and proofs * Verifies given aggregate and proofs and publishes them on appropriate gossipsub topic. */ publishAggregateAndProofs: Endpoint< "POST", {signedAggregateAndProofs: SignedAggregateAndProofListPhase0}, {body: unknown}, EmptyResponseData, EmptyMeta >; /** * Publish multiple aggregate and proofs * Verifies given aggregate and proofs and publishes them on appropriate gossipsub topic. */ publishAggregateAndProofsV2: Endpoint< "POST", {signedAggregateAndProofs: SignedAggregateAndProofList}, {body: unknown; headers: {[MetaHeader.Version]: string}}, EmptyResponseData, EmptyMeta >; publishContributionAndProofs: Endpoint< "POST", {contributionAndProofs: SignedContributionAndProofList}, {body: unknown}, EmptyResponseData, EmptyMeta >; /** * Signal beacon node to prepare for a committee subnet * After beacon node receives this request, * search using discv5 for peers related to this subnet * and replace current peers with those ones if necessary * If validator `is_aggregator`, beacon node must: * - announce subnet topic subscription on gossipsub * - aggregate attestations received on that subnet * * Returns if slot signature is valid and beacon node has prepared the attestation subnet. * * Note that we cannot be certain the Beacon node will find peers for that subnet for various reasons. */ prepareBeaconCommitteeSubnet: Endpoint< "POST", {subscriptions: BeaconCommitteeSubscriptionList}, {body: unknown}, EmptyResponseData, EmptyMeta >; prepareSyncCommitteeSubnets: Endpoint< "POST", {subscriptions: SyncCommitteeSubscriptionList}, {body: unknown}, EmptyResponseData, EmptyMeta >; prepareBeaconProposer: Endpoint< "POST", {proposers: ProposerPreparationDataList}, {body: unknown}, EmptyResponseData, EmptyMeta >; /** * Determine if a distributed validator has been selected to aggregate attestations * * This endpoint is implemented by a distributed validator middleware client to exchange * partial beacon committee selection proofs for combined/aggregated selection proofs to allow * a validator client to correctly determine if one of its validators has been selected to * perform an aggregation duty in this slot. * * Validator clients running in a distributed validator cluster must query this endpoint * at the start of an epoch for the current and lookahead (next) epochs for all validators * that have attester duties in the current and lookahead epochs. * * Note that this endpoint is not implemented by the beacon node and will return a 501 error * * Returns an array of threshold aggregated beacon committee selection proofs */ submitBeaconCommitteeSelections: Endpoint< "POST", { /** An array of partial beacon committee selection proofs */ selections: BeaconCommitteeSelectionList; }, {body: unknown}, BeaconCommitteeSelectionList, EmptyMeta >; /** * Determine if a distributed validator has been selected to make a sync committee contribution * * This endpoint is implemented by a distributed validator middleware client to exchange * partial sync committee selection proofs for combined/aggregated selection proofs to allow * a validator client to correctly determine if one of its validators has been selected to * perform a sync committee contribution (sync aggregation) duty in this slot. * * Validator clients running in a distributed validator cluster must query this endpoint * at the start of each slot for all validators that are included in the current sync committee. * * Note that this endpoint is not implemented by the beacon node and will return a 501 error * * Returns an array of threshold aggregated sync committee selection proofs */ submitSyncCommitteeSelections: Endpoint< "POST", { /** An array of partial sync committee selection proofs */ selections: SyncCommitteeSelectionList; }, {body: unknown}, SyncCommitteeSelectionList, EmptyMeta >; /** Returns validator indices that have been observed to be active on the network */ getLiveness: Endpoint< "POST", { epoch: Epoch; indices: ValidatorIndex[]; }, {params: {epoch: Epoch}; body: unknown}, LivenessResponseDataList, EmptyMeta >; registerValidator: Endpoint< "POST", {registrations: SignedValidatorRegistrationV1List}, {body: unknown}, EmptyResponseData, EmptyMeta >; }; export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> { return { getAttesterDuties: { url: "/eth/v1/validator/duties/attester/{epoch}", method: "POST", req: { writeReqJson: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.toJson(indices)}), parseReqJson: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.fromJson(body)}), writeReqSsz: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.serialize(indices)}), parseReqSsz: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.deserialize(body)}), schema: { params: {epoch: Schema.UintRequired}, body: Schema.StringArray, }, }, resp: { data: AttesterDutyListType, meta: ExecutionOptimisticAndDependentRootCodec, }, }, getProposerDuties: { url: "/eth/v1/validator/duties/proposer/{epoch}", method: "GET", req: { writeReq: ({epoch}) => ({params: {epoch}}), parseReq: ({params}) => ({epoch: params.epoch}), schema: { params: {epoch: Schema.UintRequired}, }, }, resp: { data: ProposerDutyListType, meta: ExecutionOptimisticAndDependentRootCodec, }, }, getProposerDutiesV2: { url: "/eth/v2/validator/duties/proposer/{epoch}", method: "GET", req: { writeReq: ({epoch}) => ({params: {epoch}}), parseReq: ({params}) => ({epoch: params.epoch}), schema: { params: {epoch: Schema.UintRequired}, }, }, resp: { data: ProposerDutyListType, meta: ExecutionOptimisticAndDependentRootCodec, }, }, getSyncCommitteeDuties: { url: "/eth/v1/validator/duties/sync/{epoch}", method: "POST", req: { writeReqJson: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.toJson(indices)}), parseReqJson: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.fromJson(body)}), writeReqSsz: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.serialize(indices)}), parseReqSsz: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.deserialize(body)}), schema: { params: {epoch: Schema.UintRequired}, body: Schema.StringArray, }, }, resp: { data: SyncDutyListType, meta: ExecutionOptimisticCodec, }, }, produceBlockV3: { url: "/eth/v3/validator/blocks/{slot}", method: "GET", req: { writeReq: ({ slot, randaoReveal, graffiti, skipRandaoVerification, feeRecipient, builderSelection, builderBoostFactor, strictFeeRecipientCheck, blindedLocal, }) => ({ params: {slot}, query: { randao_reveal: toHex(randaoReveal), graffiti: toGraffitiHex(graffiti), skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification), fee_recipient: feeRecipient, builder_selection: builderSelection, builder_boost_factor: builderBoostFactor?.toString(), strict_fee_recipient_check: strictFeeRecipientCheck, blinded_local: blindedLocal, }, }), parseReq: ({params, query}) => ({ slot: params.slot, randaoReveal: fromHex(query.randao_reveal), graffiti: fromGraffitiHex(query.graffiti), skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification), feeRecipient: query.fee_recipient, builderSelection: query.builder_selection as BuilderSelection, builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor), strictFeeRecipientCheck: query.strict_fee_recipient_check, blindedLocal: query.blinded_local, }), schema: { params: {slot: Schema.UintRequired}, query: { randao_reveal: Schema.StringRequired, graffiti: Schema.String, skip_randao_verification: Schema.String, fee_recipient: Schema.String, builder_selection: Schema.String, builder_boost_factor: Schema.String, strict_fee_recipient_check: Schema.Boolean, blinded_local: Schema.Boolean, }, }, }, resp: { // The spec defines the response as `preDeneb.BeaconBlock | postDeneb.BlockContents` // We represent the response as `{block: preDeneb.BeaconBlock} | postDeneb.BlockContents` (aka BlockContents in our codebase) // Due to this discripancy, we require a hand-written codec to handle the transformation. data: { toJson(data, {executionPayloadBlinded, version}) { return executionPayloadBlinded ? getPostBellatrixForkTypes(version).BlindedBeaconBlock.toJson(data as BlindedBeaconBlock) : isForkPostDeneb(version) ? sszTypesFor(version).BlockContents.toJson(data as BlockContents<ForkPostDeneb>) : (ssz[version].BeaconBlock as Type<BeaconBlock<ForkPreDeneb>>).toJson( (data as BlockContents).block as BeaconBlock<ForkPreDeneb> // <- tranformation ); }, fromJson(data, {executionPayloadBlinded, version}) { return executionPayloadBlinded ? getPostBellatrixForkTypes(version).BlindedBeaconBlock.fromJson(data) : isForkPostDeneb(version) ? sszTypesFor(version).BlockContents.fromJson(data) : {block: ssz[version].BeaconBlock.fromJson(data)}; // <- tranformation }, serialize(data, {executionPayloadBlinded, version}) { return executionPayloadBlinded ? getPostBellatrixForkTypes(version).BlindedBeaconBlock.serialize(data as BlindedBeaconBlock) : isForkPostDeneb(version) ? sszTypesFor(version).BlockContents.serialize(data as BlockContents<ForkPostDeneb>) : (ssz[version].BeaconBlock as Type<BeaconBlock<ForkPreDeneb>>).serialize( (data as BlockContents).block as BeaconBlock<ForkPreDeneb> // <- tranformation ); }, deserialize(data, {executionPayloadBlinded, version}) { return executionPayloadBlinded ? getPostBellatrixForkTypes(version).BlindedBeaconBlock.deserialize(data) : isForkPostDeneb(version) ? sszTypesFor(version).BlockContents.deserialize(data) : {block: ssz[version].BeaconBlock.deserialize(data)}; // <- tranformation }, }, meta: { toJson: (meta) => ({ ...ProduceBlockV3MetaType.toJson(meta), execution_payload_source: meta.executionPayloadSource, }), fromJson: (val) => { const {executionPayloadBlinded, ...meta} = ProduceBlockV3MetaType.fromJson(val); // Extract source from the data and assign defaults in the spec compliant manner if not present const executionPayloadSource = (val as {execution_payload_source: ProducedBlockSource}).execution_payload_source ?? (executionPayloadBlinded === true ? ProducedBlockSource.builder : ProducedBlockSource.engine); return {...meta, executionPayloadBlinded, executionPayloadSource}; }, toHeadersObject: (meta) => ({ [MetaHeader.Version]: meta.version, [MetaHeader.ExecutionPayloadBlinded]: meta.executionPayloadBlinded.toString(), [MetaHeader.ExecutionPayloadSource]: meta.executionPayloadSource.toString(), [MetaHeader.ExecutionPayloadValue]: meta.executionPayloadValue.toString(), [MetaHeader.ConsensusBlockValue]: meta.consensusBlockValue.toString(), }), fromHeaders: (headers) => { const executionPayloadBlinded = toBoolean(headers.getRequired(MetaHeader.ExecutionPayloadBlinded)); // Extract source from the headers and assign defaults in a spec compliant manner if not present const executionPayloadSource = (headers.get(MetaHeader.ExecutionPayloadSource) as ProducedBlockSource) ?? (executionPayloadBlinded === true ? ProducedBlockSource.builder : ProducedBlockSource.engine); return { version: toForkName(headers.getRequired(MetaHeader.Version)), executionPayloadBlinded, executionPayloadSource, executionPayloadValue: BigInt(headers.getRequired(MetaHeader.ExecutionPayloadValue)), consensusBlockValue: BigInt(headers.getRequired(MetaHeader.ConsensusBlockValue)), }; }, }, }, }, produceBlockV4: { url: "/eth/v4/validator/blocks/{slot}", method: "GET", req: { writeReq: ({ slot, randaoReveal, graffiti, skipRandaoVerification, feeRecipient, builderSelection, builderBoostFactor, strictFeeRecipientCheck, }) => ({ params: {slot}, query: { randao_reveal: toHex(randaoReveal), graffiti: toGraffitiHex(graffiti), skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification), fee_recipient: feeRecipient, builder_selection: builderSelection, builder_boost_factor: builderBoostFactor?.toString(), strict_fee_recipient_check: strictFeeRecipientCheck, }, }), parseReq: ({params, query}) => ({ slot: params.slot, randaoReveal: fromHex(query.randao_reveal), graffiti: fromGraffitiHex(query.graffiti), skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification), feeRecipient: query.fee_recipient, builderSelection: query.builder_selection as BuilderSelection, builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor), strictFeeRecipientCheck: query.strict_fee_recipient_check, }), schema: { params: {slot: Schema.UintRequired}, query: { randao_reveal: Schema.StringRequired, graffiti: Schema.String, skip_randao_verification: Schema.String, fee_recipient: Schema.String, builder_selection: Schema.String, builder_boost_factor: Schema.String, strict_fee_recipient_check: Schema.Boolean, }, }, }, resp: { data: WithVersion((fork) => getPostGloasForkTypes(fork).BeaconBlock), meta: { toJson: (meta) => ProduceBlockV4MetaType.toJson(meta), fromJson: (val) => ProduceBlockV4MetaType.fromJson(val), toHeadersObject: (meta) => ({ [MetaHeader.Version]: meta.version, [MetaHeader.ConsensusBlockValue]: meta.consensusBlockValue.toString(), }), fromHeaders: (headers) => ({ version: toForkName(headers.getRequired(MetaHeader.Version)), consensusBlockValue: BigInt(headers.getRequired(MetaHeader.ConsensusBlockValue)), }), }, }, }, getExecutionPayloadEnvelope: { url: "/eth/v1/validator/execution_payload_envelope/{slot}/{beacon_block_root}", method: "GET", req: { writeReq: ({slot, beaconBlockRoot}) => ({ params: { slot, beacon_block_root: toRootHex(beaconBlockRoot), }, }), parseReq: ({params}) => ({ slot: params.slot, beaconBlockRoot: fromHex(params.beacon_block_root), }), schema: { params: { slot: Schema.UintRequired, beacon_block_root: Schema.StringRequired, }, }, }, resp: { data: ssz.gloas.ExecutionPayloadEnvelope, meta: VersionCodec, }, }, produceAttestationData: { url: "/eth/v1/validator/attestation_data", method: "GET", req: { writeReq: ({committeeIndex, slot}) => ({query: {slot, committee_index: committeeIndex}}), parseReq: ({query}) => ({committeeIndex: query.committee_index, slot: query.slot}), schema: { query: {slot: Schema.UintRequired, committee_index: Schema.Uint}, }, }, resp: { data: ssz.phase0.AttestationData, meta: EmptyMetaCodec, }, }, produceSyncCommitteeContribution: { url: "/eth/v1/validator/sync_committee_contribution", method: "GET", req: { writeReq: ({slot, subcommitteeIndex, beaconBlockRoot}) => ({ query: {slot, subcommittee_index: subcommitteeIndex, beacon_block_root: toRootHex(beaconBlockRoot)}, }), parseReq: ({query}) => ({ slot: query.slot, subcommitteeIndex: query.subcommittee_index, beaconBlockRoot: fromHex(query.beacon_block_root), }), schema: { query: { slot: Schema.UintRequired, subcommittee_index: Schema.UintRequired, beacon_block_root: Schema.StringRequired, }, }, }, resp: { data: ssz.altair.SyncCommitteeContribution, meta: EmptyMetaCodec, }, }, getAggregatedAttestation: { url: "/eth/v1/validator/aggregate_attestation", method: "GET", req: { writeReq: ({attestationDataRoot, slot}) => ({ query: {attestation_data_root: toRootHex(attestationDataRoot), slot}, }), parseReq: ({query}) => ({ attestationDataRoot: fromHex(query.attestation_data_root), slot: query.slot, }), schema: { query: { attestation_data_root: Schema.StringRequired, slot: Schema.UintRequired, }, }, }, resp: { data: ssz.phase0.Attestation, meta: EmptyMetaCodec, }, }, getAggregatedAttestationV2: { url: "/eth/v2/validator/aggregate_attestation", method: "GET", req: { writeReq: ({attestationDataRoot, slot, committeeIndex}) => ({ query: {attestation_data_root: toHex(attestationDataRoot), slot, committee_index: committeeIndex}, }), parseReq: ({query}) => ({ attestationDataRoot: fromHex(query.attestation_data_root), slot: query.slot, committeeIndex: query.committee_index, }), schema: { query: { attestation_data_root: Schema.StringRequired, slot: Schema.UintRequired, committee_index: Schema.UintRequired, }, }, }, resp: { data: WithVersion((fork) => (isForkPostElectra(fork) ? ssz.electra.Attestation : ssz.phase0.Attestation)), meta: VersionCodec, }, }, publishAggregateAndProofs: { url: "/eth/v1/validator/aggregate_and_proofs", method: "POST", req: { writeReqJson: ({signedAggregateAndProofs}) => ({ body: SignedAggregateAndProofListPhase0Type.toJson(signedAggregateAndProofs), }), parseReqJson: ({body}) => ({ signedAggregateAndProofs: SignedAggregateAndProofListPhase0Type.fromJson(body), }), writeReqSsz: ({signedAggregateAndProofs}) => ({ body: SignedAggregateAndProofListPhase0Type.serialize(signedAggregateAndProofs), }), parseReqSsz: ({body}) => ({ signedAggregateAndProofs: SignedAggregateAndProofListPhase0Type.deserialize(body), }), schema: { body: Schema.ObjectArray, }, }, resp: EmptyResponseCodec, }, publishAggregateAndProofsV2: { url: "/eth/v2/validator/aggregate_and_proofs", method: "POST", req: { writeReqJson: ({signedAggregateAndProofs}) => { const fork = config.getForkName(signedAggregateAndProofs[0]?.message.aggregate.data.slot ?? 0); return { body: isForkPostElectra(fork) ? SignedAggregateAndProofListElectraType.toJson( signedAggregateAndProofs as SignedAggregateAndProofListElectra ) : SignedAggregateAndProofListPhase0Type.toJson( signedAggregateAndProofs as SignedAggregateAndProofListPhase0 ), headers: {[MetaHeader.Version]: fork}, }; }, parseReqJson: ({body, headers}) => { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { signedAggregateAndProofs: isForkPostElectra(fork) ? SignedAggregateAndProofListElectraType.fromJson(body) : SignedAggregateAndProofListPhase0Type.fromJson(body), }; }, writeReqSsz: ({signedAggregateAndProofs}) => { const fork = config.getForkName(signedAggregateAndProofs[0]?.message.aggregate.data.slot ?? 0); return { body: isForkPostElectra(fork) ? SignedAggregateAndProofListElectraType.serialize( signedAggregateAndProofs as SignedAggregateAndProofListElectra ) : SignedAggregateAndProofListPhase0Type.serialize( signedAggregateAndProofs as SignedAggregateAndProofListPhase0 ), headers: {[MetaHeader.Version]: fork}, }; }, parseReqSsz: ({body, headers}) => { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { signedAggregateAndProofs: isForkPostElectra(fork) ? SignedAggregateAndProofListElectraType.deserialize(body) : SignedAggregateAndProofListPhase0Type.deserialize(body), }; }, schema: { body: Schema.ObjectArray, headers: {[MetaHeader.Version]: Schema.String}, }, }, resp: EmptyResponseCodec, }, publishContributionAndProofs: { url: "/eth/v1/validator/contribution_and_proofs", method: "POST", req: { writeReqJson: ({contributionAndProofs}) => ({ body: SignedContributionAndProofListType.toJson(contributionAndProofs), }), parseReqJson: ({body}) => ({contributionAndProofs: SignedContributionAndProofListType.fromJson(body)}), writeReqSsz: ({contributionAndProofs}) => ({ body: SignedContributionAndProofListType.serialize(contributionAndProofs), }), parseReqSsz: ({body}) => ({contributionAndProofs: SignedContributionAndProofListType.deserialize(body)}), schema: { body: Schema.ObjectArray, }, }, resp: EmptyResponseCodec, }, prepareBeaconCommitteeSubnet: { url: "/eth/v1/validator/beacon_committee_subscriptions", method: "POST", req: { writeReqJson: ({subscriptions}) => ({body: BeaconCommitteeSubscriptionListType.toJson(subscriptions)}), parseReqJson: ({body}) => ({subscriptions: BeaconCommitteeSubscriptionListType.fromJson(body)}), writeReqSsz: ({subscriptions}) => ({body: BeaconCommitteeSubscriptionListType.serialize(subscriptions)}), parseReqSsz: ({body}) => ({subscriptions: BeaconCommitteeSubscriptionListType.deserialize(body)}), schema: {body: Schema.ObjectArray}, }, resp: EmptyResponseCodec, }, prepareSyncCommitteeSubnets: { url: "/eth/v1/validator/sync_committee_subscriptions", method: "POST", req: { writeReqJson: ({subscriptions}) => ({body: SyncCommitteeSubscriptionListType.toJson(subscriptions)}), parseReqJson: ({body}) => ({subscriptions: SyncCommitteeSubscriptionListType.fromJson(body)}), writeReqSsz: ({subscriptions}) => ({body: SyncCommitteeSubscriptionListType.serialize(subscriptions)}), parseReqSsz: ({body}) => ({subscriptions: SyncCommitteeSubscriptionListType.deserialize(body)}), schema: {body: Schema.ObjectArray}, }, resp: EmptyResponseCodec, }, prepareBeaconProposer: { url: "/eth/v1/validator/prepare_beacon_proposer", method: "POST", req: JsonOnlyReq({ writeReqJson: ({proposers}) => ({body: ProposerPreparationDataListType.toJson(proposers)}), parseReqJson: ({body}) => ({proposers: ProposerPreparationDataListType.fromJson(body)}), schema: {body: Schema.ObjectArray}, }), resp: EmptyResponseCodec, }, submitBeaconCommitteeSelections: { url: "/eth/v1/validator/beacon_committee_selections", method: "POST", req: { writeReqJson: ({selections}) => ({body: BeaconCommitteeSelectionListType.toJson(selections)}), parseReqJson: ({body}) => ({selections: BeaconCommitteeSelectionListType.fromJson(body)}), writeReqSsz: ({selections}) => ({body: BeaconCommitteeSelectionListType.serialize(selections)}), parseReqSsz: ({body}) => ({selections: BeaconCommitteeSelectionListType.deserialize(body)}), schema: { body: Schema.ObjectArray, }, }, resp: { data: BeaconCommitteeSelectionListType, meta: EmptyMetaCodec, }, }, submitSyncCommitteeSelections: { url: "/eth/v1/validator/sync_committee_selections", method: "POST", req: { writeReqJson: ({selections}) => ({body: SyncCommitteeSelectionListType.toJson(selections)}), parseReqJson: ({body}) => ({selections: SyncCommitteeSelectionListType.fromJson(body)}), writeReqSsz: ({selections}) => ({body: SyncCommitteeSelectionListType.serialize(selections)}), parseReqSsz: ({body}) => ({selections: SyncCommitteeSelectionListType.deserialize(body)}), schema: { body: Schema.ObjectArray, }, }, resp: { data: SyncCommitteeSelectionListType, meta: EmptyMetaCodec, }, }, getLiveness: { url: "/eth/v1/validator/liveness/{epoch}", method: "POST", req: { writeReqJson: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.toJson(indices)}), parseReqJson: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.fromJson(body)}), writeReqSsz: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.serialize(indices)}), parseReqSsz: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.deserialize(body)}), schema: { params: {epoch: Schema.UintRequired}, body: Schema.StringArray, }, }, resp: { data: LivenessResponseDataListType, meta: EmptyMetaCodec, }, }, registerValidator: { url: "/eth/v1/validator/register_validator", method: "POST", req: { writeReqJson: ({registrations}) => ({body: SignedValidatorRegistrationV1ListType.toJson(registrations)}), parseReqJson: ({body}) => ({registrations: SignedValidatorRegistrationV1ListType.fromJson(body)}), writeReqSsz: ({registrations}) => ({body: SignedValidatorRegistrationV1ListType.serialize(registrations)}), parseReqSsz: ({body}) => ({registrations: SignedValidatorRegistrationV1ListType.deserialize(body)}), schema: { body: Schema.ObjectArray, }, }, resp: EmptyResponseCodec, init: { requestWireFormat: WireFormat.ssz, }, }, }; } function parseBuilderBoostFactor(builderBoostFactorInput?: string | number | bigint): bigint | undefined { return builderBoostFactorInput !== undefined ? BigInt(builderBoostFactorInput) : undefined; } function writeSkipRandaoVerification(skipRandaoVerification?: boolean): string | undefined { return skipRandaoVerification === true ? "" : undefined; } function parseSkipRandaoVerification(skipRandaoVerification?: string): boolean { return skipRandaoVerification !== undefined && skipRandaoVerification === ""; }