Skip to content

Commit

Permalink
Move proof endpoints to new namespace (#4771)
Browse files Browse the repository at this point in the history
* Move proofs endpoints to new namespace

* Rename proofs -> proof

* Fix renaming issues

Co-authored-by: Cayman <[email protected]>
  • Loading branch information
dapplion and wemeetagain authored Nov 17, 2022
1 parent 331a8e6 commit f74462e
Show file tree
Hide file tree
Showing 23 changed files with 212 additions and 130 deletions.
2 changes: 2 additions & 0 deletions packages/api/src/beacon/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as events from "./events.js";
import * as lightclient from "./lightclient.js";
import * as lodestar from "./lodestar.js";
import * as node from "./node.js";
import * as proof from "./proof.js";
import * as validator from "./validator.js";

type ClientModules = HttpClientModules & {
Expand All @@ -31,6 +32,7 @@ export function getClient(opts: HttpClientOptions, modules: ClientModules): Api
lightclient: lightclient.getClient(config, httpClient),
lodestar: lodestar.getClient(config, httpClient),
node: node.getClient(config, httpClient),
proof: proof.getClient(config, httpClient),
validator: validator.getClient(config, httpClient),
};
}
20 changes: 3 additions & 17 deletions packages/api/src/beacon/client/lightclient.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import {IChainForkConfig} from "@lodestar/config";
import {deserializeProof} from "@chainsafe/persistent-merkle-tree";
import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "../routes/lightclient.js";
import {IHttpClient, getFetchOptsSerializers, generateGenericJsonClient} from "../../utils/client/index.js";
import {IHttpClient, generateGenericJsonClient} from "../../utils/client/index.js";

/**
* REST HTTP client for lightclient routes
*/
export function getClient(_config: IChainForkConfig, httpClient: IHttpClient): Api {
const reqSerializers = getReqSerializers();
const returnTypes = getReturnTypes();

// Some routes return JSON, use a client auto-generator
const client = generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient);
// For `getStateProof()` generate request serializer
const fetchOptsSerializers = getFetchOptsSerializers<Api, ReqTypes>(routesData, reqSerializers);

return {
...client,

async getStateProof(stateId, paths) {
const buffer = await httpClient.arrayBuffer(fetchOptsSerializers.getStateProof(stateId, paths));
const proof = deserializeProof(new Uint8Array(buffer));
return {data: proof};
},
};
// All routes return JSON, use a client auto-generator
return generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient);
}
22 changes: 22 additions & 0 deletions packages/api/src/beacon/client/proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {IChainForkConfig} from "@lodestar/config";
import {deserializeProof} from "@chainsafe/persistent-merkle-tree";
import {Api, ReqTypes, routesData, getReqSerializers} from "../routes/proof.js";
import {IHttpClient, getFetchOptsSerializers} from "../../utils/client/index.js";

/**
* REST HTTP client for lightclient routes
*/
export function getClient(_config: IChainForkConfig, httpClient: IHttpClient): Api {
const reqSerializers = getReqSerializers();

// For `getStateProof()` generate request serializer
const fetchOptsSerializers = getFetchOptsSerializers<Api, ReqTypes>(routesData, reqSerializers);

return {
async getStateProof(stateId, paths) {
const buffer = await httpClient.arrayBuffer(fetchOptsSerializers.getStateProof(stateId, paths));
const proof = deserializeProof(new Uint8Array(buffer));
return {data: proof};
},
};
}
1 change: 1 addition & 0 deletions packages/api/src/beacon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const allNamespacesObj: {[K in keyof Api]: true} = {
lightclient: true,
lodestar: true,
node: true,
proof: true,
validator: true,
};
export const allNamespaces = Object.keys(allNamespacesObj) as ApiNamespace[];
3 changes: 3 additions & 0 deletions packages/api/src/beacon/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Api as EventsApi} from "./events.js";
import {Api as LightclientApi} from "./lightclient.js";
import {Api as LodestarApi} from "./lodestar.js";
import {Api as NodeApi} from "./node.js";
import {Api as ProofApi} from "./proof.js";
import {Api as ValidatorApi} from "./validator.js";

export * as beacon from "./beacon/index.js";
Expand All @@ -14,6 +15,7 @@ export * as events from "./events.js";
export * as lightclient from "./lightclient.js";
export * as lodestar from "./lodestar.js";
export * as node from "./node.js";
export * as proof from "./proof.js";
export * as validator from "./validator.js";

export type Api = {
Expand All @@ -24,6 +26,7 @@ export type Api = {
lightclient: LightclientApi;
lodestar: LodestarApi;
node: NodeApi;
proof: ProofApi;
validator: ValidatorApi;
};

Expand Down
19 changes: 0 additions & 19 deletions packages/api/src/beacon/routes/lightclient.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import {JsonPath} from "@chainsafe/ssz";
import {Proof} from "@chainsafe/persistent-merkle-tree";
import {altair, phase0, ssz, SyncPeriod} from "@lodestar/types";
import {
ArrayOf,
ReturnTypes,
RoutesData,
Schema,
sameType,
ContainerData,
ReqSerializers,
reqEmpty,
ReqEmpty,
} from "../../utils/index.js";
import {queryParseProofPathsArr, querySerializeProofPathsArr} from "../../utils/serdes.js";

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

Expand All @@ -24,11 +20,6 @@ export type LightClientBootstrap = {
};

export type Api = {
/**
* Returns a multiproof of `jsonPaths` at the requested `stateId`.
* The requested `stateId` may not be available. Regular nodes only keep recent states in memory.
*/
getStateProof(stateId: string, jsonPaths: JsonPath[]): Promise<{data: Proof}>;
/**
* Returns an array of best updates given a `startPeriod` and `count` number of sync committee period to return.
* Best is defined by (in order of priority):
Expand Down Expand Up @@ -59,7 +50,6 @@ export type Api = {
* Define javascript values for each route
*/
export const routesData: RoutesData<Api> = {
getStateProof: {url: "/eth/v1/beacon/light_client/proof/{state_id}", method: "GET"},
getUpdates: {url: "/eth/v1/beacon/light_client/updates", method: "GET"},
getOptimisticUpdate: {url: "/eth/v1/beacon/light_client/optimistic_update", method: "GET"},
getFinalityUpdate: {url: "/eth/v1/beacon/light_client/finality_update", method: "GET"},
Expand All @@ -69,7 +59,6 @@ export const routesData: RoutesData<Api> = {

/* eslint-disable @typescript-eslint/naming-convention */
export type ReqTypes = {
getStateProof: {params: {state_id: string}; query: {paths: string[]}};
getUpdates: {query: {start_period: number; count: number}};
getOptimisticUpdate: ReqEmpty;
getFinalityUpdate: ReqEmpty;
Expand All @@ -79,12 +68,6 @@ export type ReqTypes = {

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
return {
getStateProof: {
writeReq: (state_id, paths) => ({params: {state_id}, query: {paths: querySerializeProofPathsArr(paths)}}),
parseReq: ({params, query}) => [params.state_id, queryParseProofPathsArr(query.paths)],
schema: {params: {state_id: Schema.StringRequired}, body: Schema.AnyArray},
},

getUpdates: {
writeReq: (start_period, count) => ({query: {start_period, count}}),
parseReq: ({query}) => [query.start_period, query.count],
Expand All @@ -109,8 +92,6 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {

export function getReturnTypes(): ReturnTypes<Api> {
return {
// Just sent the proof JSON as-is
getStateProof: sameType(),
getUpdates: ContainerData(ArrayOf(ssz.altair.LightClientUpdate)),
getOptimisticUpdate: ContainerData(ssz.altair.LightClientOptimisticUpdate),
getFinalityUpdate: ContainerData(ssz.altair.LightClientFinalityUpdate),
Expand Down
43 changes: 43 additions & 0 deletions packages/api/src/beacon/routes/proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {JsonPath} from "@chainsafe/ssz";
import {Proof} from "@chainsafe/persistent-merkle-tree";
import {ReturnTypes, RoutesData, Schema, sameType, ReqSerializers} from "../../utils/index.js";
import {queryParseProofPathsArr, querySerializeProofPathsArr} from "../../utils/serdes.js";

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

export type Api = {
/**
* Returns a multiproof of `jsonPaths` at the requested `stateId`.
* The requested `stateId` may not be available. Regular nodes only keep recent states in memory.
*/
getStateProof(stateId: string, jsonPaths: JsonPath[]): Promise<{data: Proof}>;
};

/**
* Define javascript values for each route
*/
export const routesData: RoutesData<Api> = {
getStateProof: {url: "/eth/v0/beacon/proof/state/{state_id}", method: "GET"},
};

/* eslint-disable @typescript-eslint/naming-convention */
export type ReqTypes = {
getStateProof: {params: {state_id: string}; query: {paths: string[]}};
};

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
return {
getStateProof: {
writeReq: (state_id, paths) => ({params: {state_id}, query: {paths: querySerializeProofPathsArr(paths)}}),
parseReq: ({params, query}) => [params.state_id, queryParseProofPathsArr(query.paths)],
schema: {params: {state_id: Schema.StringRequired}, body: Schema.AnyArray},
},
};
}

export function getReturnTypes(): ReturnTypes<Api> {
return {
// Just sent the proof JSON as-is
getStateProof: sameType(),
};
}
2 changes: 2 additions & 0 deletions packages/api/src/beacon/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as events from "./events.js";
import * as lightclient from "./lightclient.js";
import * as lodestar from "./lodestar.js";
import * as node from "./node.js";
import * as proof from "./proof.js";
import * as validator from "./validator.js";

// Re-export for convenience
Expand Down Expand Up @@ -36,6 +37,7 @@ export function registerRoutes(
lightclient: () => lightclient.getRoutes(config, api.lightclient),
lodestar: () => lodestar.getRoutes(config, api.lodestar),
node: () => node.getRoutes(config, api.node),
proof: () => proof.getRoutes(config, api.proof),
validator: () => validator.getRoutes(config, api.validator),
};

Expand Down
24 changes: 2 additions & 22 deletions packages/api/src/beacon/server/lightclient.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
import {IChainForkConfig} from "@lodestar/config";
import {serializeProof} from "@chainsafe/persistent-merkle-tree";
import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/lightclient.js";
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";

export function getRoutes(config: IChainForkConfig, api: Api): ServerRoutes<Api, ReqTypes> {
const reqSerializers = getReqSerializers();
const serverRoutes = getGenericJsonServer<Api, ReqTypes>(
{routesData, getReturnTypes, getReqSerializers},
config,
api
);

return {
...serverRoutes,

// Non-JSON routes. Return binary
getStateProof: {
...serverRoutes.getStateProof,
handler: async (req) => {
const args = reqSerializers.getStateProof.parseReq(req);
const {data: proof} = await api.getStateProof(...args);
// Fastify 3.x.x will automatically add header `Content-Type: application/octet-stream` if Buffer
return Buffer.from(serializeProof(proof));
},
},
};
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<Api, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
26 changes: 26 additions & 0 deletions packages/api/src/beacon/server/proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {IChainForkConfig} from "@lodestar/config";
import {serializeProof} from "@chainsafe/persistent-merkle-tree";
import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/proof.js";
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";

export function getRoutes(config: IChainForkConfig, api: Api): ServerRoutes<Api, ReqTypes> {
const reqSerializers = getReqSerializers();
const serverRoutes = getGenericJsonServer<Api, ReqTypes>(
{routesData, getReturnTypes, getReqSerializers},
config,
api
);

return {
// Non-JSON routes. Return binary
getStateProof: {
...serverRoutes.getStateProof,
handler: async (req) => {
const args = reqSerializers.getStateProof.parseReq(req);
const {data: proof} = await api.getStateProof(...args);
// Fastify 3.x.x will automatically add header `Content-Type: application/octet-stream` if Buffer
return Buffer.from(serializeProof(proof));
},
},
};
}
10 changes: 10 additions & 0 deletions packages/api/test/unit/beacon/genericServerTest/proofs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {config} from "@lodestar/config/default";
import {Api, ReqTypes} from "../../../../src/beacon/routes/proof.js";
import {getClient} from "../../../../src/beacon/client/proof.js";
import {getRoutes} from "../../../../src/beacon/server/proof.js";
import {runGenericServerTest} from "../../../utils/genericServerTest.js";
import {testData} from "../testData/proofs.js";

describe("beacon / proofs", () => {
runGenericServerTest<Api, ReqTypes>(config, getClient, getRoutes, testData);
});
5 changes: 5 additions & 0 deletions packages/api/test/unit/beacon/oapiSpec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {testData as debugTestData} from "./testData/debug.js";
import {eventTestData, testData as eventsTestData} from "./testData/events.js";
import {testData as lightclientTestData} from "./testData/lightclient.js";
import {testData as nodeTestData} from "./testData/node.js";
import {testData as proofsTestData} from "./testData/proofs.js";
import {testData as validatorTestData} from "./testData/validator.js";

// Global variable __dirname no longer available in ES6 modules.
Expand All @@ -36,6 +37,7 @@ const routesData = {
...routes.events.routesData,
...routes.lightclient.routesData,
...routes.node.routesData,
...routes.proof.routesData,
...routes.validator.routesData,
};

Expand All @@ -57,6 +59,7 @@ const reqSerializers = {
...getEventsReqSerializers(),
...routes.lightclient.getReqSerializers(),
...routes.node.getReqSerializers(),
...routes.proof.getReqSerializers(),
...routes.validator.getReqSerializers(),
};

Expand All @@ -66,6 +69,7 @@ const returnTypes = {
...routes.debug.getReturnTypes(),
...routes.lightclient.getReturnTypes(),
...routes.node.getReturnTypes(),
...routes.proof.getReturnTypes(),
...routes.validator.getReturnTypes(),
};

Expand All @@ -76,6 +80,7 @@ const testDatas = {
...eventsTestData,
...lightclientTestData,
...nodeTestData,
...proofsTestData,
...validatorTestData,
};

Expand Down
25 changes: 0 additions & 25 deletions packages/api/test/unit/beacon/testData/lightclient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {ssz} from "@lodestar/types";
import {ProofType} from "@chainsafe/persistent-merkle-tree";
import {toHexString} from "@chainsafe/ssz";
import {Api} from "../../../../src/beacon/routes/lightclient.js";
import {GenericServerTestCases} from "../../../utils/genericServerTest.js";
Expand All @@ -12,30 +11,6 @@ const header = ssz.phase0.BeaconBlockHeader.defaultValue();
const signatureSlot = ssz.Slot.defaultValue();

export const testData: GenericServerTestCases<Api> = {
getStateProof: {
args: [
"head",
[
// ["validator", 0, "balance"],
["finalized_checkpoint", 0, "root", 12000],
],
],
res: {
data: {
type: ProofType.treeOffset,
offsets: [1, 2, 3],
leaves: [root, root, root, root],
},
},
/* eslint-disable quotes */
query: {
paths: [
// '["validator",0,"balance"]',
'["finalized_checkpoint",0,"root",12000]',
],
},
/* eslint-enable quotes */
},
getUpdates: {
args: [1, 2],
res: {data: [lightClientUpdate]},
Expand Down
Loading

0 comments on commit f74462e

Please sign in to comment.