Skip to content

Commit

Permalink
Merge 7180c6f into f9cb593
Browse files Browse the repository at this point in the history
  • Loading branch information
dapplion authored Jan 22, 2022
2 parents f9cb593 + 7180c6f commit 2534af3
Show file tree
Hide file tree
Showing 51 changed files with 395 additions and 370 deletions.
2 changes: 1 addition & 1 deletion packages/api/src/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "../r
*/
export function getClient(config: IChainForkConfig, httpClient: IHttpClient): Api {
const reqSerializers = getReqSerializers();
const returnTypes = getReturnTypes(config);
const returnTypes = getReturnTypes();
// All routes return JSON, use a client auto-generator
return generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient);
}
39 changes: 12 additions & 27 deletions packages/api/src/routes/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {IBeaconPreset, BeaconPreset, activePreset} from "@chainsafe/lodestar-params";
import {IChainConfig, ChainConfig} from "@chainsafe/lodestar-config";
import {BeaconPreset} from "@chainsafe/lodestar-params";
import {IChainConfig} from "@chainsafe/lodestar-config";
import {Bytes32, Number64, phase0, ssz} from "@chainsafe/lodestar-types";
import {mapValues} from "@chainsafe/lodestar-utils";
import {ByteVectorType, ContainerType, Json, Type} from "@chainsafe/ssz";
import {ArrayOf, ContainerData, ReqEmpty, reqEmpty, ReturnTypes, ReqSerializers, RoutesData, TypeJson} from "../utils";
import {ByteVectorType, ContainerType} from "@chainsafe/ssz";
import {ArrayOf, ContainerData, ReqEmpty, reqEmpty, ReturnTypes, ReqSerializers, RoutesData, sameType} from "../utils";

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

Expand All @@ -12,16 +12,7 @@ export type DepositContract = {
address: Bytes32;
};

export type ISpec = IBeaconPreset & IChainConfig;

// eslint-disable-next-line @typescript-eslint/naming-convention
export const Spec = new ContainerType<ISpec>({
fields: {
...BeaconPreset.fields,
...ChainConfig.fields,
},
expectedCase: "notransform",
});
export type Spec = BeaconPreset & IChainConfig;

export type Api = {
/**
Expand All @@ -37,15 +28,16 @@ export type Api = {
getForkSchedule(): Promise<{data: phase0.Fork[]}>;

/**
* Get spec params.
* Retrieve specification configuration used on this node.
* [Specification params list](https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/configs/mainnet/phase0.yaml)
* Retrieve specification configuration used on this node. The configuration should include:
* - Constants for all hard forks known by the beacon node, for example the [phase 0](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#constants) and [altair](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#constants) values
* - Presets for all hard forks supplied to the beacon node, for example the [phase 0](https://github.com/ethereum/eth2.0-specs/blob/dev/presets/mainnet/phase0.yaml) and [altair](https://github.com/ethereum/eth2.0-specs/blob/dev/presets/mainnet/altair.yaml) values
* - Configuration for the beacon node, for example the [mainnet](https://github.com/ethereum/eth2.0-specs/blob/dev/configs/mainnet.yaml) values
*
* Values are returned with following format:
* - any value starting with 0x in the spec is returned as a hex string
* - numeric values are returned as a quoted integer
*/
getSpec(): Promise<{data: ISpec}>;
getSpec(): Promise<{data: Record<string, string>}>;
};

/**
Expand All @@ -63,15 +55,8 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
return mapValues(routesData, () => reqEmpty);
}

function withJsonFilled<T>(dataType: Type<T>, fillWith: Json): TypeJson<{data: T}> {
return {
toJson: ({data}, opts) => ({data: dataType.toJson(data, opts)}),
fromJson: ({data}: {data: Json}, opts) => ({data: dataType.fromJson(Object.assign({}, fillWith, data), opts)}),
};
}

/* eslint-disable @typescript-eslint/naming-convention */
export function getReturnTypes(config: IChainConfig): ReturnTypes<Api> {
export function getReturnTypes(): ReturnTypes<Api> {
const DepositContract = new ContainerType<DepositContract>({
fields: {
chainId: ssz.Number64,
Expand All @@ -87,6 +72,6 @@ export function getReturnTypes(config: IChainConfig): ReturnTypes<Api> {
return {
getDepositContract: ContainerData(DepositContract),
getForkSchedule: ContainerData(ArrayOf(ssz.phase0.Fork)),
getSpec: withJsonFilled(Spec, Spec.toJson({...config, ...activePreset})),
getSpec: ContainerData(sameType()),
};
}
15 changes: 12 additions & 3 deletions packages/api/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ContainerType, IJsonOptions, Json, ListType, Type} from "@chainsafe/ssz";
import {IJsonOptions, Json, ListType, Type} from "@chainsafe/ssz";
import {ForkName} from "@chainsafe/lodestar-params";
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {objectToExpectedCase} from "@chainsafe/lodestar-utils";
Expand Down Expand Up @@ -111,8 +111,17 @@ export function ArrayOf<T>(elementType: Type<T>, limit = 1e6): ListType<T[]> {
* data: T
* ```
*/
export function ContainerData<T>(dataType: Type<T>): ContainerType<{data: T}> {
return new ContainerType({fields: {data: dataType}, expectedCase: "notransform"});
export function ContainerData<T>(dataType: TypeJson<T>): TypeJson<{data: T}> {
return {
toJson: ({data}, opts) => ({
data: dataType.toJson(data, opts),
}),
fromJson: ({data}: {data: Json}, opts) => {
return {
data: dataType.fromJson(data, opts),
};
},
};
}

/**
Expand Down
35 changes: 27 additions & 8 deletions packages/api/test/unit/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {ssz} from "@chainsafe/lodestar-types";
import {config} from "@chainsafe/lodestar-config/default";
import {Api, ReqTypes, Spec} from "../../src/routes/config";
import {chainConfigToJson} from "@chainsafe/lodestar-config";
import {config, chainConfig} from "@chainsafe/lodestar-config/default";
import {activePreset, presetToJson} from "@chainsafe/lodestar-params";
import {Api, ReqTypes, getReturnTypes} from "../../src/routes/config";
import {getClient} from "../../src/client/config";
import {getRoutes} from "../../src/server/config";
import {runGenericServerTest} from "../utils/genericServerTest";
import {expect} from "chai";

/* eslint-disable @typescript-eslint/naming-convention */

describe("config", () => {
it("Spec casing check", function () {
const defaultSpec = Spec.defaultValue();
const specJson = Spec.toJson(defaultSpec) as Record<string, string>;
expect(specJson["SLOTS_PER_EPOCH"] !== undefined).to.be.true;
});
const configJson = chainConfigToJson(chainConfig);
const presetJson = presetToJson(activePreset);
const jsonSpec = {...configJson, ...presetJson};

runGenericServerTest<Api, ReqTypes>(config, getClient, getRoutes, {
getDepositContract: {
Expand All @@ -29,7 +31,24 @@ describe("config", () => {
},
getSpec: {
args: [],
res: {data: Spec.defaultValue()},
res: {data: jsonSpec},
},
});

it("Serialize Partial Spec object", () => {
const returnTypes = getReturnTypes();

const partialJsonSpec: Record<string, string> = {
PRESET_BASE: "mainnet",
DEPOSIT_CONTRACT_ADDRESS: "0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b",
GENESIS_FORK_VERSION: "0x00001020",
TERMINAL_TOTAL_DIFFICULTY: "115792089237316195423570985008687907853269984665640564039457584007913129639936",
MIN_GENESIS_TIME: "1606824000",
};

const jsonRes = returnTypes.getSpec.toJson({data: partialJsonSpec});
const specRes = returnTypes.getSpec.fromJson(jsonRes);

expect(specRes).to.deep.equal({data: partialJsonSpec}, "Wrong toJson -> fromJson");
});
});
15 changes: 7 additions & 8 deletions packages/cli/src/config/beaconParams.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {
ChainConfig,
IChainConfig,
createIChainForkConfig,
createIChainConfig,
IChainForkConfig,
IChainConfig,
parsePartialIChainConfigJson,
chainConfigFromJson,
} from "@chainsafe/lodestar-config";
import {writeFile, readFile} from "../util";
import {readFile} from "../util";
import {getNetworkBeaconParams, NetworkName} from "../networks";
import {getGlobalPaths, IGlobalPaths} from "../paths/global";
import {IBeaconParamsUnparsed} from "./types";
Expand Down Expand Up @@ -77,10 +76,10 @@ export function getBeaconParams({network, paramsFile, additionalParamsCli}: IBea
});
}

export function writeBeaconParams(filepath: string, params: IChainConfig): void {
writeFile(filepath, ChainConfig.toJson(params));
}

function readBeaconParams(filepath: string): IBeaconParamsUnparsed {
return readFile(filepath) ?? {};
}

export function parsePartialIChainConfigJson(input: Record<string, unknown>): Partial<IChainConfig> {
return chainConfigFromJson(input);
}
17 changes: 7 additions & 10 deletions packages/cli/src/options/paramsOptions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Options} from "yargs";
import {IChainConfig, chainConfigTypes} from "@chainsafe/lodestar-config";
import {IBeaconParamsUnparsed} from "../config/types";
import {ObjectKeys, ICliCommandOptions} from "../util";
import {ChainConfig, IChainConfig} from "@chainsafe/lodestar-config";

// No options are statically declared
// If an arbitraty key notation is used, it removes typesafety on most of this CLI arg parsing code.
Expand All @@ -17,17 +17,14 @@ export type IParamsArgs = Record<never, never> & ITerminalPowArgs;
const getArgKey = (key: keyof IBeaconParamsUnparsed): string => `params.${key}`;

export function parseBeaconParamsArgs(args: Record<string, string | number>): IBeaconParamsUnparsed {
return ((ObjectKeys(ChainConfig.fields) as unknown) as (keyof IChainConfig)[]).reduce(
(beaconParams: Partial<IBeaconParamsUnparsed>, key) => {
const value = args[getArgKey(key)];
if (value != null) beaconParams[key] = value;
return beaconParams;
},
{}
);
return ObjectKeys(chainConfigTypes).reduce((beaconParams: Partial<IBeaconParamsUnparsed>, key) => {
const value = args[getArgKey(key)];
if (value != null) beaconParams[key] = value;
return beaconParams;
}, {});
}

const paramsOptionsByName = ((ObjectKeys(ChainConfig.fields) as unknown) as (keyof IChainConfig)[]).reduce(
const paramsOptionsByName = ObjectKeys(chainConfigTypes).reduce(
(options: Record<string, Options>, key): Record<string, Options> => ({
...options,
[getArgKey(key)]: {
Expand Down
9 changes: 4 additions & 5 deletions packages/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

> This package is part of [ChainSafe's Lodestar](https://lodestar.chainsafe.io) project

Lodestar defines all [network configuration variables](https://github.com/ethereum/eth2.0-specs/tree/dev/configs) defined in the [Ethereum Consensus / Eth2 spec](https://github.com/ethereum/eth2.0-specs). This tooling may be used to configure testnets, ingest mainnet/testnet config variables, or be used in downstream Lodestar libraries.

## Installation
Expand Down Expand Up @@ -50,7 +49,7 @@ chainConfig.SECONDS_PER_SLOT === 12;
There are also utility functions to help create a `IChainConfig` from unknown input and partial configs.

```typescript
import {createIChainConfig, IChainConfig, parsePartialIChainConfigJson} from "@chainsafe/lodestar-config";
import {createIChainConfig, IChainConfig, chainConfigFromJson} from "@chainsafe/lodestar-config";

// example config
let chainConfigObj: Record<string, unknown> = {
Expand All @@ -76,9 +75,9 @@ let chainConfigObj: Record<string, unknown> = {
INACTIVITY_SCORE_RECOVERY_RATE: 16,
ALTAIR_FORK_VERSION: "0x01004811",
ALTAIR_FORK_EPOCH: 10,
}
};

const partialChainConfig: Partial<IChainConfig> = parsePartialIChainConfigJson(chainConfigObj);
const partialChainConfig: Partial<IChainConfig> = chainConfigFromJson(chainConfigObj);

// Fill in the missing values with mainnet default values
const chainConfig: IChainConfig = createIChainConfig(partialChainConfig);
Expand All @@ -88,7 +87,7 @@ const chainConfig: IChainConfig = createIChainConfig(partialChainConfig);

The variables described in the spec can be used to assemble a more structured 'fork schedule'. This info is organized as `IForkConfig` in the Lodestar config package. In practice, the `IChainConfig` and `IForkConfig` are usually combined as a `IChainForkConfig`.

A `IForkConfig` provides methods to select the fork info, fork name, fork version, or fork ssz types given a slot.
A `IForkConfig` provides methods to select the fork info, fork name, fork version, or fork ssz types given a slot.

```typescript
import {GENESIS_SLOT} from "@chainsafe/lodestar-params";
Expand Down
21 changes: 1 addition & 20 deletions packages/config/src/chainConfig/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {Json} from "@chainsafe/ssz";
import {ACTIVE_PRESET} from "@chainsafe/lodestar-params";
import {IChainConfig} from "./types";
import {ChainConfig} from "./sszTypes";
import {defaultChainConfig} from "./default";

export {chainConfigToJson, chainConfigFromJson} from "./json";
export * from "./types";
export * from "./sszTypes";
export * from "./default";

/**
Expand All @@ -27,20 +25,3 @@ export function createIChainConfig(input: Partial<IChainConfig>): IChainConfig {
}
return config;
}

export function parsePartialIChainConfigJson(input?: Record<string, unknown>): Partial<IChainConfig> {
if (!input) {
return {};
}

const config = {};

// Parse config input values, if they exist
for (const [fieldName, fieldType] of Object.entries(ChainConfig.fields)) {
if (input[fieldName] != null) {
(config as Record<string, unknown>)[fieldName] = fieldType.fromJson(input[fieldName] as Json);
}
}

return config;
}
78 changes: 78 additions & 0 deletions packages/config/src/chainConfig/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {fromHexString, toHexString} from "@chainsafe/ssz";
import {IChainConfig, chainConfigTypes, SpecValue, SpecValueTypeName} from "./types";

const MAX_UINT64_JSON = "18446744073709551615";

export function chainConfigToJson(config: IChainConfig): Record<string, string> {
const json: Record<string, string> = {};

for (const key of Object.keys(config) as (keyof IChainConfig)[]) {
json[key] = serializeSpecValue(config[key], chainConfigTypes[key]);
}

return json;
}

export function chainConfigFromJson(json: Record<string, unknown>): IChainConfig {
const config = {} as IChainConfig;

for (const key of Object.keys(json) as (keyof IChainConfig)[]) {
config[key] = deserializeSpecValue(json[key], chainConfigTypes[key]) as never;
}

return config;
}

export function serializeSpecValue(value: SpecValue, typeName: SpecValueTypeName): string {
switch (typeName) {
case "number":
if (typeof value !== "number") {
throw Error(`Invalid value ${value} expected number`);
}
if (value === Infinity) {
return MAX_UINT64_JSON;
}
return value.toString(10);

case "bigint":
if (typeof value !== "bigint") {
throw Error(`Invalid value ${value} expected bigint`);
}
return value.toString(10);

case "bytes":
if (!(value instanceof Uint8Array)) {
throw Error(`Invalid value ${value} expected Uint8Array`);
}
return toHexString(value);

case "string":
if (typeof value !== "string") {
throw Error(`Invalid value ${value} expected string`);
}
return value;
}
}

export function deserializeSpecValue(valueStr: unknown, typeName: SpecValueTypeName): SpecValue {
if (typeof valueStr !== "string") {
throw Error(`Invalid value ${valueStr} expected string`);
}

switch (typeName) {
case "number":
if (valueStr === MAX_UINT64_JSON) {
return Infinity;
}
return parseInt(valueStr, 10);

case "bigint":
return BigInt(valueStr);

case "bytes":
return fromHexString(valueStr);

case "string":
return valueStr;
}
}
2 changes: 1 addition & 1 deletion packages/config/src/chainConfig/networks/mainnet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {fromHexString as b} from "@chainsafe/ssz";
import {IChainConfig} from "..";
import {IChainConfig} from "../types";
import {chainConfig as mainnet} from "../presets/mainnet";

/* eslint-disable max-len */
Expand Down
2 changes: 1 addition & 1 deletion packages/config/src/chainConfig/networks/prater.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {fromHexString as b} from "@chainsafe/ssz";
import {IChainConfig} from "..";
import {IChainConfig} from "../types";
import {chainConfig as mainnet} from "../presets/mainnet";

/* eslint-disable max-len */
Expand Down
Loading

0 comments on commit 2534af3

Please sign in to comment.