Skip to content

Commit

Permalink
refactor: move validator status type and util to @lodestar/types (#7140)
Browse files Browse the repository at this point in the history
  • Loading branch information
nflaig authored and ensi321 committed Oct 10, 2024
1 parent e6e6642 commit 73d9959
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 153 deletions.
14 changes: 2 additions & 12 deletions packages/api/src/beacon/routes/beacon/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import {ContainerType, ValueOf} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params";
import {phase0, CommitteeIndex, Slot, Epoch, ssz, RootHex, StringType} from "@lodestar/types";
import {phase0, CommitteeIndex, Slot, Epoch, ssz, RootHex, StringType, ValidatorStatus} from "@lodestar/types";
import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js";
import {ArrayOf, JsonOnlyReq} from "../../../utils/codecs.js";
import {ExecutionOptimisticAndFinalizedCodec, ExecutionOptimisticAndFinalizedMeta} from "../../../utils/metadata.js";
Expand All @@ -24,17 +24,7 @@ export type StateArgs = {

export type ValidatorId = string | number;

export type ValidatorStatus =
| "active"
| "pending_initialized"
| "pending_queued"
| "active_ongoing"
| "active_exiting"
| "active_slashed"
| "exited_unslashed"
| "exited_slashed"
| "withdrawal_possible"
| "withdrawal_done";
export type {ValidatorStatus};

export const RandaoResponseType = new ContainerType({
randao: ssz.Root,
Expand Down
9 changes: 2 additions & 7 deletions packages/beacon-node/src/api/impl/beacon/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@ import {
getRandaoMix,
} from "@lodestar/state-transition";
import {EPOCHS_PER_HISTORICAL_VECTOR} from "@lodestar/params";
import {getValidatorStatus} from "@lodestar/types";
import {fromHex} from "@lodestar/utils";
import {ApiError} from "../../errors.js";
import {ApiModules} from "../../types.js";
import {
filterStateValidatorsByStatus,
getStateValidatorIndex,
getValidatorStatus,
getStateResponse,
toValidatorResponse,
} from "./utils.js";
import {filterStateValidatorsByStatus, getStateValidatorIndex, getStateResponse, toValidatorResponse} from "./utils.js";

export function getBeaconStateApi({
chain,
Expand Down
36 changes: 2 additions & 34 deletions packages/beacon-node/src/api/impl/beacon/state/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
import {routes} from "@lodestar/api";
import {FAR_FUTURE_EPOCH, GENESIS_SLOT} from "@lodestar/params";
import {GENESIS_SLOT} from "@lodestar/params";
import {BeaconStateAllForks} from "@lodestar/state-transition";
import {BLSPubkey, Epoch, phase0, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
import {BLSPubkey, Epoch, getValidatorStatus, phase0, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
import {fromHex} from "@lodestar/utils";
import {CheckpointWithHex, IForkChoice} from "@lodestar/fork-choice";
import {IBeaconChain} from "../../../../chain/index.js";
Expand Down Expand Up @@ -83,38 +83,6 @@ export async function getStateResponseWithRegen(
return res;
}

/**
* 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 {
// pending
if (validator.activationEpoch > currentEpoch) {
if (validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH) {
return "pending_initialized";
} else if (validator.activationEligibilityEpoch < FAR_FUTURE_EPOCH) {
return "pending_queued";
}
}
// active
if (validator.activationEpoch <= currentEpoch && currentEpoch < validator.exitEpoch) {
if (validator.exitEpoch === FAR_FUTURE_EPOCH) {
return "active_ongoing";
} else if (validator.exitEpoch < FAR_FUTURE_EPOCH) {
return validator.slashed ? "active_slashed" : "active_exiting";
}
}
// exited
if (validator.exitEpoch <= currentEpoch && currentEpoch < validator.withdrawableEpoch) {
return validator.slashed ? "exited_slashed" : "exited_unslashed";
}
// withdrawal
if (validator.withdrawableEpoch <= currentEpoch) {
return validator.effectiveBalance !== 0 ? "withdrawal_possible" : "withdrawal_done";
}
throw new Error("ValidatorStatus unknown");
}

export function toValidatorResponse(
index: ValidatorIndex,
validator: phase0.Validator,
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
BeaconBlock,
BlockContents,
BlindedBeaconBlock,
getValidatorStatus,
} from "@lodestar/types";
import {ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice";
import {fromHex, toHex, resolveOrRacePromises, prettyWeiToEth, toRootHex} from "@lodestar/utils";
Expand All @@ -61,7 +62,6 @@ import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/va
import {CommitteeSubscription} from "../../../network/subnets/index.js";
import {ApiModules} from "../types.js";
import {RegenCaller} from "../../../chain/regen/index.js";
import {getValidatorStatus} from "../beacon/state/utils.js";
import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
import {SCHEDULER_LOOKAHEAD_FACTOR} from "../../../chain/prepareNextSlot.js";
import {ChainEvent, CheckpointHex, CommonBlockBody} from "../../../chain/index.js";
Expand Down
100 changes: 1 addition & 99 deletions packages/beacon-node/test/unit/api/impl/beacon/state/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,107 +1,9 @@
import {describe, it, expect} from "vitest";
import {toHexString} from "@chainsafe/ssz";
import {phase0} from "@lodestar/types";
import {getValidatorStatus, getStateValidatorIndex} from "../../../../../../src/api/impl/beacon/state/utils.js";
import {getStateValidatorIndex} from "../../../../../../src/api/impl/beacon/state/utils.js";
import {generateCachedAltairState} from "../../../../../utils/state.js";

describe("beacon state api utils", function () {
describe("getValidatorStatus", function () {
it("should return PENDING_INITIALIZED", function () {
const validator = {
activationEpoch: 1,
activationEligibilityEpoch: Infinity,
} as phase0.Validator;
const currentEpoch = 0;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("pending_initialized");
});
it("should return PENDING_QUEUED", function () {
const validator = {
activationEpoch: 1,
activationEligibilityEpoch: 101010101101010,
} as phase0.Validator;
const currentEpoch = 0;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("pending_queued");
});
it("should return ACTIVE_ONGOING", function () {
const validator = {
activationEpoch: 1,
exitEpoch: Infinity,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("active_ongoing");
});
it("should return ACTIVE_SLASHED", function () {
const validator = {
activationEpoch: 1,
exitEpoch: 101010101101010,
slashed: true,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("active_slashed");
});
it("should return ACTIVE_EXITING", function () {
const validator = {
activationEpoch: 1,
exitEpoch: 101010101101010,
slashed: false,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("active_exiting");
});
it("should return EXITED_SLASHED", function () {
const validator = {
exitEpoch: 1,
withdrawableEpoch: 3,
slashed: true,
} as phase0.Validator;
const currentEpoch = 2;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("exited_slashed");
});
it("should return EXITED_UNSLASHED", function () {
const validator = {
exitEpoch: 1,
withdrawableEpoch: 3,
slashed: false,
} as phase0.Validator;
const currentEpoch = 2;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("exited_unslashed");
});
it("should return WITHDRAWAL_POSSIBLE", function () {
const validator = {
withdrawableEpoch: 1,
effectiveBalance: 32,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("withdrawal_possible");
});
it("should return WITHDRAWAL_DONE", function () {
const validator = {
withdrawableEpoch: 1,
effectiveBalance: 0,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("withdrawal_done");
});
it("should error", function () {
const validator = {} as phase0.Validator;
const currentEpoch = 0;
try {
getValidatorStatus(validator, currentEpoch);
} catch (error) {
expect(error).toHaveProperty("message", "ValidatorStatus unknown");
}
});
});

describe("getStateValidatorIndex", () => {
const state = generateCachedAltairState();
const pubkey2index = state.epochCtx.pubkey2index;
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./utils/typeguards.js";
export {StringType, stringType} from "./utils/stringType.js";
// Container utils
export * from "./utils/container.js";
export * from "./utils/validatorStatus.js";
46 changes: 46 additions & 0 deletions packages/types/src/utils/validatorStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {FAR_FUTURE_EPOCH} from "@lodestar/params";
import {Epoch, phase0} from "../types.js";

export type ValidatorStatus =
| "active"
| "pending_initialized"
| "pending_queued"
| "active_ongoing"
| "active_exiting"
| "active_slashed"
| "exited_unslashed"
| "exited_slashed"
| "withdrawal_possible"
| "withdrawal_done";

/**
* Get the status of the validator
* based on conditions outlined in https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ
*/
export function getValidatorStatus(validator: phase0.Validator, currentEpoch: Epoch): ValidatorStatus {
// pending
if (validator.activationEpoch > currentEpoch) {
if (validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH) {
return "pending_initialized";
} else if (validator.activationEligibilityEpoch < FAR_FUTURE_EPOCH) {
return "pending_queued";
}
}
// active
if (validator.activationEpoch <= currentEpoch && currentEpoch < validator.exitEpoch) {
if (validator.exitEpoch === FAR_FUTURE_EPOCH) {
return "active_ongoing";
} else if (validator.exitEpoch < FAR_FUTURE_EPOCH) {
return validator.slashed ? "active_slashed" : "active_exiting";
}
}
// exited
if (validator.exitEpoch <= currentEpoch && currentEpoch < validator.withdrawableEpoch) {
return validator.slashed ? "exited_slashed" : "exited_unslashed";
}
// withdrawal
if (validator.withdrawableEpoch <= currentEpoch) {
return validator.effectiveBalance !== 0 ? "withdrawal_possible" : "withdrawal_done";
}
throw new Error("ValidatorStatus unknown");
}
100 changes: 100 additions & 0 deletions packages/types/test/unit/validatorStatus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {describe, it, expect} from "vitest";
import {getValidatorStatus} from "../../src/utils/validatorStatus.js";
import {phase0} from "../../src/types.js";

describe("getValidatorStatus", function () {
it("should return PENDING_INITIALIZED", function () {
const validator = {
activationEpoch: 1,
activationEligibilityEpoch: Infinity,
} as phase0.Validator;
const currentEpoch = 0;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("pending_initialized");
});
it("should return PENDING_QUEUED", function () {
const validator = {
activationEpoch: 1,
activationEligibilityEpoch: 101010101101010,
} as phase0.Validator;
const currentEpoch = 0;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("pending_queued");
});
it("should return ACTIVE_ONGOING", function () {
const validator = {
activationEpoch: 1,
exitEpoch: Infinity,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("active_ongoing");
});
it("should return ACTIVE_SLASHED", function () {
const validator = {
activationEpoch: 1,
exitEpoch: 101010101101010,
slashed: true,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("active_slashed");
});
it("should return ACTIVE_EXITING", function () {
const validator = {
activationEpoch: 1,
exitEpoch: 101010101101010,
slashed: false,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("active_exiting");
});
it("should return EXITED_SLASHED", function () {
const validator = {
exitEpoch: 1,
withdrawableEpoch: 3,
slashed: true,
} as phase0.Validator;
const currentEpoch = 2;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("exited_slashed");
});
it("should return EXITED_UNSLASHED", function () {
const validator = {
exitEpoch: 1,
withdrawableEpoch: 3,
slashed: false,
} as phase0.Validator;
const currentEpoch = 2;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("exited_unslashed");
});
it("should return WITHDRAWAL_POSSIBLE", function () {
const validator = {
withdrawableEpoch: 1,
effectiveBalance: 32,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("withdrawal_possible");
});
it("should return WITHDRAWAL_DONE", function () {
const validator = {
withdrawableEpoch: 1,
effectiveBalance: 0,
} as phase0.Validator;
const currentEpoch = 1;
const status = getValidatorStatus(validator, currentEpoch);
expect(status).toBe("withdrawal_done");
});
it("should error", function () {
const validator = {} as phase0.Validator;
const currentEpoch = 0;
try {
getValidatorStatus(validator, currentEpoch);
} catch (error) {
expect(error).toHaveProperty("message", "ValidatorStatus unknown");
}
});
});

0 comments on commit 73d9959

Please sign in to comment.