Skip to content

Commit

Permalink
Merge 4c3c71c into ed794b4
Browse files Browse the repository at this point in the history
  • Loading branch information
wemeetagain authored Sep 22, 2023
2 parents ed794b4 + 4c3c71c commit ba92e6f
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 9 deletions.
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
node_modules

# Docs
assets
# assets are useful for swagger-ui and relatively lightweight
# https://github.com/ChainSafe/lodestar/pull/5970#discussion_r1334420451
# assets
supporting-docs
docs
typedocs
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/beacon/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function registerRoutes(
}

for (const route of Object.values(routes())) {
registerRoute(server, route);
registerRoute(server, route, namespace);
}
}
}
6 changes: 4 additions & 2 deletions packages/api/src/utils/server/registerRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {ServerInstance, RouteConfig, ServerRoute} from "./types.js";
export function registerRoute(
server: ServerInstance,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
route: ServerRoute<any>
route: ServerRoute<any>,
namespace?: string
): void {
server.route({
url: route.url,
method: route.method,
handler: route.handler,
schema: route.schema,
// append the namespace as a tag for downstream consumption of our API schema, eg: for swagger UI
schema: {...route.schema, ...(namespace ? {tags: [namespace]} : undefined), operationId: route.id},
config: {operationId: route.id} as RouteConfig,
});
}
2 changes: 2 additions & 0 deletions packages/beacon-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@
"@ethersproject/abi": "^5.7.0",
"@fastify/bearer-auth": "^9.0.0",
"@fastify/cors": "^8.2.1",
"@fastify/swagger": "^8.10.0",
"@fastify/swagger-ui": "^1.9.3",
"@libp2p/bootstrap": "^9.0.2",
"@libp2p/interface": "^0.1.1",
"@libp2p/mdns": "^9.0.2",
Expand Down
3 changes: 2 additions & 1 deletion packages/beacon-node/src/api/rest/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type RestApiServerOpts = {
bearerToken?: string;
headerLimit?: number;
bodyLimit?: number;
swaggerUI?: boolean;
};

export type RestApiServerModules = {
Expand All @@ -38,7 +39,7 @@ export class RestApiServer {
private readonly activeSockets: HttpActiveSocketsTracker;

constructor(
private readonly opts: RestApiServerOpts,
protected readonly opts: RestApiServerOpts,
modules: RestApiServerModules
) {
// Apply opts defaults
Expand Down
15 changes: 14 additions & 1 deletion packages/beacon-node/src/api/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {ErrorAborted, Logger} from "@lodestar/utils";
import {ChainForkConfig} from "@lodestar/config";
import {NodeIsSyncing} from "../impl/errors.js";
import {RestApiServer, RestApiServerModules, RestApiServerMetrics, RestApiServerOpts} from "./base.js";
import {registerSwaggerUIRoutes} from "./swaggerUI.js";

export {allNamespaces} from "@lodestar/api";

export type BeaconRestApiServerOpts = Omit<RestApiServerOpts, "bearerToken"> & {
Expand Down Expand Up @@ -33,13 +35,24 @@ export type BeaconRestApiServerModules = RestApiServerModules & {
* REST API powered by `fastify` server.
*/
export class BeaconRestApiServer extends RestApiServer {
readonly opts: BeaconRestApiServerOpts;
readonly modules: BeaconRestApiServerModules;

constructor(optsArg: Partial<BeaconRestApiServerOpts>, modules: BeaconRestApiServerModules) {
const opts = {...beaconRestApiServerOpts, ...optsArg};

super(opts, modules);

this.opts = opts;
this.modules = modules;
}

async registerRoutes(version?: string): Promise<void> {
if (this.opts.swaggerUI) {
await registerSwaggerUIRoutes(this.server, this.opts, version);
}
// Instantiate and register the routes with matching namespace in `opts.api`
registerRoutes(this.server, modules.config, modules.api, opts.api);
registerRoutes(this.server, this.modules.config, this.modules.api, this.opts.api);
}

protected shouldIgnoreError(err: Error): boolean {
Expand Down
81 changes: 81 additions & 0 deletions packages/beacon-node/src/api/rest/swaggerUI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {FastifyInstance} from "fastify";
import {BeaconRestApiServerOpts} from "./index.js";

export async function registerSwaggerUIRoutes(
server: FastifyInstance,
opts: BeaconRestApiServerOpts,
version = ""
): Promise<void> {
await server.register(await import("@fastify/swagger"), {
openapi: {
info: {
title: "Lodestar API",
description: "API specification for the Lodestar beacon node",
version,
contact: {
name: "Lodestar Github",
url: "https://github.com/chainsafe/lodestar",
},
},
externalDocs: {
url: "https://chainsafe.github.io/lodestar",
description: "Lodestar documentation",
},
tags: opts.api.map((namespace) => ({name: namespace})),
},
});
await server.register(await import("@fastify/swagger-ui"), {
theme: {
title: "Lodestar API",
favicon: await getFavicon(),
},
logo: await getLogo(),
});
}

/**
* Fallback-friendly function to get an asset
*/
async function getAsset(name: string): Promise<Buffer | undefined> {
try {
const path = await import("node:path");
const fs = await import("node:fs/promises");
const url = await import("node:url");
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
return await fs.readFile(path.join(__dirname, "../../../../../assets/", name));
} catch (e) {
return undefined;
}
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function getFavicon() {
const content = await getAsset("round-icon.ico");
if (!content) {
return undefined;
}

return [
{
filename: "round-icon.ico",
rel: "icon",
sizes: "16x16",
type: "image/x-icon",
content,
},
];
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function getLogo() {
const content = await getAsset("lodestar_icon_text_white.png");
if (!content) {
return undefined;
}

return {
type: "image/png",
content,
};
}
1 change: 1 addition & 0 deletions packages/beacon-node/src/node/nodejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ export class BeaconNode {
metrics: metrics ? metrics.apiRest : null,
});
if (opts.api.rest.enabled) {
await restApi.registerRoutes(opts.api.version);
await restApi.listen();
}

Expand Down
9 changes: 9 additions & 0 deletions packages/beacon-node/test/unit/api/impl/swaggerUI.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {expect} from "chai";
import {getFavicon, getLogo} from "../../../../src/api/rest/swaggerUI.js";

describe("swaggerUI", () => {
it("should find the favicon and logo", async () => {
expect(await getFavicon()).to.not.be.undefined;
expect(await getLogo()).to.not.be.undefined;
});
});
4 changes: 4 additions & 0 deletions packages/cli/src/cmds/dev/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ const externalOptionsOverrides: Partial<Record<"network" | keyof typeof beaconNo
defaultDescription: undefined,
default: true,
},
"rest.swaggerUI": {
...beaconNodeOptions["rest.swaggerUI"],
default: true,
},
};

export const devOptions = {
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/options/beaconNodeOptions/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type ApiArgs = {
"rest.port": number;
"rest.headerLimit"?: number;
"rest.bodyLimit"?: number;
"rest.swaggerUI"?: boolean;
};

export function parseArgs(args: ApiArgs): IBeaconNodeOptions["api"] {
Expand All @@ -25,6 +26,7 @@ export function parseArgs(args: ApiArgs): IBeaconNodeOptions["api"] {
port: args["rest.port"],
headerLimit: args["rest.headerLimit"],
bodyLimit: args["rest.bodyLimit"],
swaggerUI: args["rest.swaggerUI"],
},
};
}
Expand Down Expand Up @@ -89,4 +91,11 @@ export const options: CliCommandOptions<ApiArgs> = {
type: "number",
description: "Defines the maximum payload, in bytes, the server is allowed to accept",
},

"rest.swaggerUI": {
type: "boolean",
description: "Enable Swagger UI for API exploration at http://{address}:{port}/documentation",
default: Boolean(defaultOptions.api.rest.swaggerUI),
group: "api",
},
};
87 changes: 84 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,11 @@
"@ethersproject/properties" "^5.7.0"
"@ethersproject/strings" "^5.7.0"

"@fastify/accept-negotiator@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz#c1c66b3b771c09742a54dd5bc87c582f6b0630ff"
integrity sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==

"@fastify/ajv-compiler@^3.5.0":
version "3.5.0"
resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz#459bff00fefbf86c96ec30e62e933d2379e46670"
Expand Down Expand Up @@ -1267,6 +1272,51 @@
dependencies:
fast-json-stringify "^5.7.0"

"@fastify/send@^2.0.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@fastify/send/-/send-2.1.0.tgz#1aa269ccb4b0940a2dadd1f844443b15d8224ea0"
integrity sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==
dependencies:
"@lukeed/ms" "^2.0.1"
escape-html "~1.0.3"
fast-decode-uri-component "^1.0.1"
http-errors "2.0.0"
mime "^3.0.0"

"@fastify/static@^6.0.0":
version "6.11.2"
resolved "https://registry.yarnpkg.com/@fastify/static/-/static-6.11.2.tgz#1fe40c40daf055a28d29db807b459fcff431d9b6"
integrity sha512-EH7mh7q4MfNdT7N07ZVlwsX/ObngMvQ7KBP0FXAuPov99Fjn80KSJMdxQhhYKAKWW1jXiFdrk8X7d6uGWdZFxg==
dependencies:
"@fastify/accept-negotiator" "^1.0.0"
"@fastify/send" "^2.0.0"
content-disposition "^0.5.3"
fastify-plugin "^4.0.0"
glob "^8.0.1"
p-limit "^3.1.0"

"@fastify/swagger-ui@^1.9.3":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@fastify/swagger-ui/-/swagger-ui-1.9.3.tgz#1ec03ea2595cb2e7d6de6ae7c949bebcff8370a5"
integrity sha512-YYqce4CydjDIEry6Zo4JLjVPe5rjS8iGnk3fHiIQnth9sFSLeyG0U1DCH+IyYmLddNDg1uWJOuErlVqnu/jI3w==
dependencies:
"@fastify/static" "^6.0.0"
fastify-plugin "^4.0.0"
openapi-types "^12.0.2"
rfdc "^1.3.0"
yaml "^2.2.2"

"@fastify/swagger@^8.10.0":
version "8.10.0"
resolved "https://registry.yarnpkg.com/@fastify/swagger/-/swagger-8.10.0.tgz#d978ae9f2d802ab652955d02be7a125f7f6d9f05"
integrity sha512-0o6nd0qWpJbVSv/vbK4bzHSYe7l+PTGPqrQVwWIXVGd7CvXr585SBx+h8EgrMOY80bcOnGreqnjYFOV0osGP5A==
dependencies:
fastify-plugin "^4.0.0"
json-schema-resolver "^2.0.0"
openapi-types "^12.0.0"
rfdc "^1.3.0"
yaml "^2.2.2"

"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3":
version "1.1.3"
resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz"
Expand Down Expand Up @@ -1732,6 +1782,11 @@
private-ip "^3.0.0"
uint8arraylist "^2.4.3"

"@lukeed/ms@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@lukeed/ms/-/ms-2.0.1.tgz#3c2bbc258affd9cc0e0cc7828477383c73afa6ee"
integrity sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==

"@multiformats/mafmt@^12.1.2":
version "12.1.6"
resolved "https://registry.yarnpkg.com/@multiformats/mafmt/-/mafmt-12.1.6.tgz#e7c1831c1e94c94932621826049afc89f3ad43b7"
Expand Down Expand Up @@ -5125,6 +5180,13 @@ constants-browserify@^1.0.0:
resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz"
integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=

content-disposition@^0.5.3:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
dependencies:
safe-buffer "5.2.1"

content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
Expand Down Expand Up @@ -8747,6 +8809,15 @@ json-parse-even-better-errors@^3.0.0:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz#2cb2ee33069a78870a0c7e3da560026b89669cf7"
integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==

json-schema-resolver@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz#d17fdf53560e6bc9af084b930fee27f6ce4a03b6"
integrity sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==
dependencies:
debug "^4.1.1"
rfdc "^1.1.4"
uri-js "^4.2.2"

json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
Expand Down Expand Up @@ -9679,6 +9750,11 @@ mime@2.6.0, mime@^2.5.2:
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==

mime@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==

mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
Expand Down Expand Up @@ -10778,6 +10854,11 @@ open@^9.1.0:
is-inside-container "^1.0.0"
is-wsl "^2.2.0"

openapi-types@^12.0.0, openapi-types@^12.0.2:
version "12.1.3"
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3"
integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==

optionator@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
Expand Down Expand Up @@ -10864,7 +10945,7 @@ p-limit@^2.2.0:
dependencies:
p-try "^2.0.0"

p-limit@^3.0.2:
p-limit@^3.0.2, p-limit@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
Expand Down Expand Up @@ -12010,7 +12091,7 @@ rewiremock@^3.14.5:
wipe-node-cache "^2.1.2"
wipe-webpack-cache "^2.1.0"

rfdc@^1.2.0, rfdc@^1.3.0:
rfdc@^1.1.4, rfdc@^1.2.0, rfdc@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
Expand Down Expand Up @@ -12108,7 +12189,7 @@ rxjs@^7.8.0:
dependencies:
tslib "^2.1.0"

safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
Expand Down

0 comments on commit ba92e6f

Please sign in to comment.