Skip to content

Commit

Permalink
Follow beacon HTTP API spec (#3864)
Browse files Browse the repository at this point in the history
* return the numeric values in /eth/v1/node/syncing as string to confirm to spec. Fixes #3846

* use query name as specified in beacon node api for filtering validators. Fixes #3852

* minor cleaning up in tests
  • Loading branch information
dadepo authored Mar 23, 2022
1 parent 94c8e12 commit cdfebc3
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 22 deletions.
14 changes: 7 additions & 7 deletions packages/api/src/routes/beacon/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type ValidatorStatus =
| "withdrawal_done";

export type ValidatorFilters = {
indices?: ValidatorId[];
id?: ValidatorId[];
statuses?: ValidatorStatus[];
};
export type CommitteesFilters = {
Expand Down Expand Up @@ -153,8 +153,8 @@ export type ReqTypes = {
getStateFork: StateIdOnlyReq;
getStateRoot: StateIdOnlyReq;
getStateValidator: {params: {stateId: StateId; validatorId: ValidatorId}};
getStateValidators: {params: {stateId: StateId}; query: {indices?: ValidatorId[]; statuses?: ValidatorStatus[]}};
getStateValidatorBalances: {params: {stateId: StateId}; query: {indices?: ValidatorId[]}};
getStateValidators: {params: {stateId: StateId}; query: {id?: ValidatorId[]; statuses?: ValidatorStatus[]}};
getStateValidatorBalances: {params: {stateId: StateId}; query: {id?: ValidatorId[]}};
};

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
Expand Down Expand Up @@ -200,16 +200,16 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
parseReq: ({params, query}) => [params.stateId, query],
schema: {
params: {stateId: Schema.StringRequired},
query: {indices: Schema.UintOrStringArray, statuses: Schema.StringArray},
query: {id: Schema.UintOrStringArray, statuses: Schema.StringArray},
},
},

getStateValidatorBalances: {
writeReq: (stateId, indices) => ({params: {stateId}, query: {indices}}),
parseReq: ({params, query}) => [params.stateId, query.indices],
writeReq: (stateId, id) => ({params: {stateId}, query: {id}}),
parseReq: ({params, query}) => [params.stateId, query.id],
schema: {
params: {stateId: Schema.StringRequired},
query: {indices: Schema.UintOrStringArray},
query: {id: Schema.UintOrStringArray},
},
},
};
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/routes/node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {allForks, Slot, ssz, StringType} from "@chainsafe/lodestar-types";
import {allForks, ssz, StringType} from "@chainsafe/lodestar-types";
import {ContainerType} from "@chainsafe/ssz";
import {
ArrayOf,
Expand Down Expand Up @@ -52,9 +52,9 @@ export type FilterGetPeers = {

export type SyncingStatus = {
/** Head slot node is trying to reach */
headSlot: Slot;
headSlot: string;
/** How many slots node needs to process to reach head. 0 if synced. */
syncDistance: Slot;
syncDistance: string;
/** Set to true if the node is syncing, false if the node is synced. */
isSyncing: boolean;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/unit/beacon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe("beacon", () => {
},
},
getStateValidators: {
args: ["head", {indices: [pubkeyHex, "1300"], statuses: ["active_ongoing"]}],
args: ["head", {id: [pubkeyHex, "1300"], statuses: ["active_ongoing"]}],
res: {data: [validatorResponse]},
},
getStateValidator: {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/unit/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe("node", () => {
},
getSyncingStatus: {
args: [],
res: {data: {headSlot: 1, syncDistance: 2, isSyncing: false}},
res: {data: {headSlot: "1", syncDistance: "2", isSyncing: false}},
},
getHealth: {
args: [],
Expand Down
4 changes: 2 additions & 2 deletions packages/lodestar/src/api/impl/beacon/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export function getBeaconStateApi({chain, config, db}: Pick<ApiModules, "chain"
const {pubkey2index} = chain.getHeadState();

const validatorResponses: routes.beacon.ValidatorResponse[] = [];
if (filters?.indices) {
for (const id of filters.indices) {
if (filters?.id) {
for (const id of filters.id) {
const validatorIndex = getStateValidatorIndex(id, state, pubkey2index);
if (validatorIndex != null) {
const validator = validators[validatorIndex];
Expand Down
8 changes: 4 additions & 4 deletions packages/lodestar/src/sync/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ export class BeaconSync implements IBeaconSync {
case SyncState.SyncingHead:
case SyncState.Stalled:
return {
headSlot: headSlot,
syncDistance: currentSlot - headSlot,
headSlot: String(headSlot),
syncDistance: String(currentSlot - headSlot),
isSyncing: true,
};
case SyncState.Synced:
return {
headSlot: headSlot,
syncDistance: 0,
headSlot: String(headSlot),
syncDistance: "0",
isSyncing: false,
};
default:
Expand Down
114 changes: 114 additions & 0 deletions packages/lodestar/test/e2e/api/impl/beacon/state/endpoint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import chaiAsPromised from "chai-as-promised";
import chai, {expect} from "chai";
import {initBLS} from "@chainsafe/lodestar-cli/src/util";
import {createIBeaconConfig, IChainConfig} from "@chainsafe/lodestar-config";
import {chainConfig as chainConfigDef} from "@chainsafe/lodestar-config/default";
import {getClient} from "@chainsafe/lodestar-api";
import {toHexString} from "@chainsafe/ssz";
import {LogLevel, testLogger, TestLoggerOpts} from "../../../../../utils/logger";
import {getDevBeaconNode} from "../../../../../utils/node/beacon";
import {getAndInitDevValidators} from "../../../../../utils/node/validator";

chai.use(chaiAsPromised);

/* eslint-disable @typescript-eslint/naming-convention */
describe("lodestar / api / impl / state", function () {
const SECONDS_PER_SLOT = 2;
const ALTAIR_FORK_EPOCH = 0;
const restPort = 9596;
const chainConfig: IChainConfig = {...chainConfigDef, SECONDS_PER_SLOT, ALTAIR_FORK_EPOCH};
const genesisValidatorsRoot = Buffer.alloc(32, 0xaa);
const config = createIBeaconConfig(chainConfig, genesisValidatorsRoot);
const testLoggerOpts: TestLoggerOpts = {logLevel: LogLevel.info};
const loggerNodeA = testLogger("Node-A", testLoggerOpts);

describe("eth/v1/beacon/states/{status_id}/validators", function () {
this.timeout("10 min");
const testParams: Pick<IChainConfig, "SECONDS_PER_SLOT"> = {
SECONDS_PER_SLOT: 2,
};

const afterEachCallbacks: (() => Promise<unknown> | void)[] = [];
afterEach(async () => {
while (afterEachCallbacks.length > 0) {
const callback = afterEachCallbacks.pop();
if (callback) await callback();
}
});

before(async function () {
await initBLS();
});

it("should return all validators when getStateValidators called without filters", async function () {
const validatorCount = 2;
const bn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
api: {rest: {enabled: true, port: restPort}},
},
validatorCount,
logger: loggerNodeA,
});
afterEachCallbacks.push(() => bn.close());

const {validators} = await getAndInitDevValidators({
node: bn,
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
});
afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.stop())));

await Promise.all(validators.map((validator) => validator.start()));

const client = getClient(config, {baseUrl: `http://127.0.0.1:${restPort}`}).beacon;

const response = await client.getStateValidators("head");
expect(response.data.length).to.be.equal(validatorCount);
expect(response.data[0].index).to.be.equal(0);
expect(response.data[1].index).to.be.equal(1);
});

it("should return filtered validators when getStateValidators called with filters", async function () {
const validatorCount = 2;
const filterPubKey =
"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c";

const bn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
api: {rest: {enabled: true, port: restPort}},
},
validatorCount,
logger: loggerNodeA,
});
afterEachCallbacks.push(() => bn.close());

const {validators} = await getAndInitDevValidators({
node: bn,
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
});
afterEachCallbacks.push(() => Promise.all(validators.map((validator) => validator.stop())));

await Promise.all(validators.map((validator) => validator.start()));

const client = getClient(config, {baseUrl: `http://127.0.0.1:${restPort}`}).beacon;

const response = await client.getStateValidators("head", {
id: [filterPubKey],
});

expect(response.data.length).to.be.equal(1);
expect(toHexString(response.data[0].validator.pubkey)).to.be.equal(filterPubKey);
});
});
});
63 changes: 63 additions & 0 deletions packages/lodestar/test/e2e/sync/endpoint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import chaiAsPromised from "chai-as-promised";
import chai, {expect} from "chai";
import {initBLS} from "@chainsafe/lodestar-cli/src/util";
import {createIBeaconConfig, IChainConfig} from "@chainsafe/lodestar-config";
import {chainConfig as chainConfigDef} from "@chainsafe/lodestar-config/default";
import {getClient} from "@chainsafe/lodestar-api";
import {getDevBeaconNode} from "../../utils/node/beacon";
import {LogLevel, testLogger, TestLoggerOpts} from "../../utils/logger";

chai.use(chaiAsPromised);

/* eslint-disable @typescript-eslint/naming-convention */
describe("lodestar / sync", function () {
const SECONDS_PER_SLOT = 2;
const ALTAIR_FORK_EPOCH = 0;
const validatorCount = 1;
const restPort = 9596;
const chainConfig: IChainConfig = {...chainConfigDef, SECONDS_PER_SLOT, ALTAIR_FORK_EPOCH};
const genesisValidatorsRoot = Buffer.alloc(32, 0xaa);
const config = createIBeaconConfig(chainConfig, genesisValidatorsRoot);
const testLoggerOpts: TestLoggerOpts = {logLevel: LogLevel.info};
const loggerNodeA = testLogger("Node-A", testLoggerOpts);

describe("/eth/v1/node/syncing", function () {
const testParams: Pick<IChainConfig, "SECONDS_PER_SLOT"> = {
SECONDS_PER_SLOT: 2,
};

const afterEachCallbacks: (() => Promise<unknown> | void)[] = [];
afterEach(async () => {
while (afterEachCallbacks.length > 0) {
const callback = afterEachCallbacks.pop();
if (callback) await callback();
}
});

before(async function () {
await initBLS();
});

it("getSyncingStatus", async function () {
this.timeout("10 min");
const bn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
api: {rest: {enabled: true, port: restPort}},
},
validatorCount,
logger: loggerNodeA,
});

afterEachCallbacks.push(() => bn.close());

const client = getClient(config, {baseUrl: "http://127.0.0.1:9596"}).node;

// expect headSlot and syncDistance to be string
await expect(client.getSyncingStatus()).to.eventually.be.deep.equal({
data: {headSlot: "0", syncDistance: "0", isSyncing: false},
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe("beacon api impl - state - validators", function () {
} as unknown) as PubkeyIndexMap,
} as CachedBeaconStateAllForks);
const api = getBeaconStateApi({config, db: dbStub, chain: chainStub});
const {data: validators} = await api.getStateValidators("someState", {indices: [0, 1, 123]});
const {data: validators} = await api.getStateValidators("someState", {id: [0, 1, 123]});
expect(validators.length).to.equal(2);
});

Expand Down Expand Up @@ -96,7 +96,7 @@ describe("beacon api impl - state - validators", function () {
}
const api = getBeaconStateApi({config, db: dbStub, chain: chainStub});
const {data: stateValidators} = await api.getStateValidators("someState", {
indices: [0, 1, 2, 123],
id: [0, 1, 2, 123],
statuses: ["pending_initialized"],
});
expect(stateValidators.length).to.equal(3);
Expand Down
2 changes: 1 addition & 1 deletion packages/validator/src/services/indices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class IndicesService {
}

private async fetchValidatorIndices(pubkeysHex: string[]): Promise<ValidatorIndex[]> {
const validatorsState = await this.api.beacon.getStateValidators("head", {indices: pubkeysHex});
const validatorsState = await this.api.beacon.getStateValidators("head", {id: pubkeysHex});
const newIndices = [];
for (const validatorState of validatorsState.data) {
const pubkeyHex = toHexString(validatorState.validator.pubkey);
Expand Down
2 changes: 1 addition & 1 deletion packages/validator/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class Validator {
* Perform a voluntary exit for the given validator by its key.
*/
async voluntaryExit(publicKey: string, exitEpoch?: number): Promise<void> {
const {data: stateValidators} = await this.api.beacon.getStateValidators("head", {indices: [publicKey]});
const {data: stateValidators} = await this.api.beacon.getStateValidators("head", {id: [publicKey]});
const stateValidator = stateValidators[0];
if (stateValidator === undefined) {
throw new Error(`Validator pubkey ${publicKey} not found in state`);
Expand Down

0 comments on commit cdfebc3

Please sign in to comment.