diff --git a/packages/api/src/beacon/routes/beacon/index.ts b/packages/api/src/beacon/routes/beacon/index.ts index 4d0c8186fd22..a2a3cceb8e54 100644 --- a/packages/api/src/beacon/routes/beacon/index.ts +++ b/packages/api/src/beacon/routes/beacon/index.ts @@ -22,7 +22,6 @@ export type {AttestationFilters} from "./pool.js"; export type { StateId, ValidatorId, - ValidatorStatus, ValidatorFilters, CommitteesFilters, FinalityCheckpoints, diff --git a/packages/api/src/beacon/routes/beacon/state.ts b/packages/api/src/beacon/routes/beacon/state.ts index 75d3549eb5a7..8719ceb44fb9 100644 --- a/packages/api/src/beacon/routes/beacon/state.ts +++ b/packages/api/src/beacon/routes/beacon/state.ts @@ -1,5 +1,16 @@ import {ContainerType} from "@chainsafe/ssz"; -import {phase0, CommitteeIndex, Slot, ValidatorIndex, Epoch, Root, ssz, StringType, RootHex} from "@lodestar/types"; +import { + phase0, + CommitteeIndex, + Slot, + ValidatorIndex, + Epoch, + Root, + ssz, + RootHex, + ValidatorStatus, + validatorStatusType, +} from "@lodestar/types"; import {ApiClientResponse} from "../../../interfaces.js"; import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; import { @@ -23,18 +34,6 @@ export type ValidatorId = string | number; */ export type ExecutionOptimistic = boolean; -export type ValidatorStatus = - | "active" - | "pending_initialized" - | "pending_queued" - | "active_ongoing" - | "active_exiting" - | "active_slashed" - | "exited_unslashed" - | "exited_slashed" - | "withdrawal_possible" - | "withdrawal_done"; - export type ValidatorFilters = { id?: ValidatorId[]; status?: ValidatorStatus[]; @@ -312,7 +311,7 @@ export function getReturnTypes(): ReturnTypes { { index: ssz.ValidatorIndex, balance: ssz.UintNum64, - status: new StringType(), + status: validatorStatusType, validator: ssz.phase0.Validator, }, {jsonCase: "eth2"} diff --git a/packages/beacon-node/src/api/impl/beacon/state/utils.ts b/packages/beacon-node/src/api/impl/beacon/state/utils.ts index 3c17fa30ac67..9c46f9f4074f 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/utils.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/utils.ts @@ -2,7 +2,7 @@ import {fromHexString} from "@chainsafe/ssz"; import {routes} from "@lodestar/api"; import {FAR_FUTURE_EPOCH, GENESIS_SLOT} from "@lodestar/params"; import {BeaconStateAllForks, PubkeyIndexMap} from "@lodestar/state-transition"; -import {BLSPubkey, phase0} from "@lodestar/types"; +import {BLSPubkey, ValidatorStatus, phase0} from "@lodestar/types"; import {Epoch, ValidatorIndex} from "@lodestar/types"; import {IBeaconChain, StateGetOpts} from "../../../../chain/index.js"; import {ApiError, ValidationError} from "../../errors.js"; @@ -66,7 +66,7 @@ async function resolveStateIdOrNull( * Get the status of the validator * based on conditions outlined in https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ */ -export function getValidatorStatus(validator: phase0.Validator, currentEpoch: Epoch): routes.beacon.ValidatorStatus { +export function getValidatorStatus(validator: phase0.Validator, currentEpoch: Epoch): ValidatorStatus { // pending if (validator.activationEpoch > currentEpoch) { if (validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH) { diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index c827e121da62..be9032354885 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -833,7 +833,6 @@ export function getValidatorApi({ const validator = headState.validators.getReadonly(validatorIndex); const status = getValidatorStatus(validator, currentEpoch); return ( - status === "active" || status === "active_exiting" || status === "active_ongoing" || status === "active_slashed" || diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 825b962c5f1f..31042c7a5c73 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -4,3 +4,5 @@ export * as ssz from "./sszTypes.js"; export * from "./utils/typeguards.js"; // String type export {StringType, stringType} from "./utils/StringType.js"; +// Validator status type +export {ValidatorStatusType, validatorStatusType} from "./utils/ValidatorStatusType.js"; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index e2e416fa3667..d58dba05e581 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -20,3 +20,17 @@ export enum ProducedBlockSource { export type SlotRootHex = {slot: Slot; root: RootHex}; export type SlotOptionalRoot = {slot: Slot; root?: RootHex}; + +/** + * [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) + */ +export type ValidatorStatus = + | "pending_initialized" + | "pending_queued" + | "active_ongoing" + | "active_exiting" + | "active_slashed" + | "exited_unslashed" + | "exited_slashed" + | "withdrawal_possible" + | "withdrawal_done"; diff --git a/packages/types/src/utils/ValidatorStatusType.ts b/packages/types/src/utils/ValidatorStatusType.ts new file mode 100644 index 000000000000..88cff0f95e9f --- /dev/null +++ b/packages/types/src/utils/ValidatorStatusType.ts @@ -0,0 +1,89 @@ +import {BasicType} from "@chainsafe/ssz"; +import {ValidatorStatus} from "../types.js"; + +// TODO: add spec reference once defined +const statusToByteMapping: Record = { + pending_initialized: 0x01, + pending_queued: 0x02, + active_ongoing: 0x03, + active_exiting: 0x04, + active_slashed: 0x05, + exited_unslashed: 0x06, + exited_slashed: 0x07, + withdrawal_possible: 0x08, + withdrawal_done: 0x09, +}; + +const byteToStatusMapping = Object.fromEntries( + Object.entries(statusToByteMapping).map(([key, value]) => [value, key]) +) as Record; + +export class ValidatorStatusType extends BasicType { + // TODO: review if those parameters are correct + readonly typeName = "ValidatorStatus"; + readonly byteLength = 1; + readonly fixedSize = 1; + readonly minSize = 1; + readonly maxSize = 1; + + defaultValue(): ValidatorStatus { + return "" as ValidatorStatus; + } + + // Serialization + deserialization + + value_serializeToBytes(output: ByteViews, offset: number, value: ValidatorStatus): number { + output.uint8Array[offset] = statusToByteMapping[value]; + return offset + 1; + } + value_deserializeFromBytes(data: ByteViews, start: number, end: number): ValidatorStatus { + this.assertValidSize(end - start); + + const status = byteToStatusMapping[data.uint8Array[start]]; + + if (status === undefined) { + throw Error(`ValidatorStatus: invalid value: ${data.uint8Array[start]}`); + } + + return status; + } + tree_serializeToBytes(): number { + throw Error("Not supported in ValidatorStatus type"); + } + tree_deserializeFromBytes(): never { + throw Error("Not supported in ValidatorStatus type"); + } + + // Fast tree opts + + tree_getFromNode(): ValidatorStatus { + throw Error("Not supported in ValidatorStatus type"); + } + tree_setToNode(): void { + throw Error("Not supported in ValidatorStatus type"); + } + tree_getFromPackedNode(): ValidatorStatus { + throw Error("Not supported in ValidatorStatus type"); + } + tree_setToPackedNode(): void { + throw Error("Not supported in ValidatorStatus type"); + } + + // JSON + + fromJson(json: unknown): ValidatorStatus { + return json as ValidatorStatus; + } + + toJson(value: ValidatorStatus): ValidatorStatus { + return value; + } +} + +// TODO: export from ssz / or move type to ssz? +type ByteViews = { + uint8Array: Uint8Array; + dataView: DataView; +}; + +export const validatorStatusType = new ValidatorStatusType(); diff --git a/packages/validator/src/services/indices.ts b/packages/validator/src/services/indices.ts index 12de1bbb9392..e24708a5b839 100644 --- a/packages/validator/src/services/indices.ts +++ b/packages/validator/src/services/indices.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; -import {ValidatorIndex} from "@lodestar/types"; +import {ValidatorIndex, ValidatorStatus} from "@lodestar/types"; import {Logger, MapDef} from "@lodestar/utils"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {Api, ApiError} from "@lodestar/api"; import {batchItems} from "../util/index.js"; import {Metrics} from "../metrics.js"; @@ -17,9 +17,8 @@ type PubkeyHex = string; // To assist with logging statuses, we only log the statuses that are not active_exiting or withdrawal_possible type SimpleValidatorStatus = "pending" | "active" | "exited" | "withdrawn"; -const statusToSimpleStatusMapping = (status: routes.beacon.ValidatorStatus): SimpleValidatorStatus => { +const statusToSimpleStatusMapping = (status: ValidatorStatus): SimpleValidatorStatus => { switch (status) { - case "active": case "active_exiting": case "active_slashed": case "active_ongoing": diff --git a/packages/validator/test/unit/services/attestationDuties.test.ts b/packages/validator/test/unit/services/attestationDuties.test.ts index 0a4a1b2c2fe9..ac76da62d71d 100644 --- a/packages/validator/test/unit/services/attestationDuties.test.ts +++ b/packages/validator/test/unit/services/attestationDuties.test.ts @@ -32,7 +32,7 @@ describe("AttestationDutiesService", function () { const defaultValidator: routes.beacon.ValidatorResponse = { index, balance: 32e9, - status: "active", + status: "active_ongoing", validator: ssz.phase0.Validator.defaultValue(), }; diff --git a/packages/validator/test/unit/services/syncCommitteDuties.test.ts b/packages/validator/test/unit/services/syncCommitteDuties.test.ts index af5734ffdcca..eed92719d3c0 100644 --- a/packages/validator/test/unit/services/syncCommitteDuties.test.ts +++ b/packages/validator/test/unit/services/syncCommitteDuties.test.ts @@ -39,7 +39,7 @@ describe("SyncCommitteeDutiesService", function () { const defaultValidator: routes.beacon.ValidatorResponse = { index: indices[0], balance: 32e9, - status: "active", + status: "active_ongoing", validator: ssz.phase0.Validator.defaultValue(), };