Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move validator status type and util to @lodestar/types #7140

Merged
merged 1 commit into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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};
Copy link
Member Author

@nflaig nflaig Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-export for backward compatibility


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
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");
}
});
});
Loading