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

Lighthouse eth2 implementation #346

Merged
merged 22 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from 6 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
21 changes: 13 additions & 8 deletions src/renderer/ducks/validator/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {getAuthAccount} from "../auth/selectors";
import {getBeaconNodes} from "../network/selectors";
import {getValidators} from "./selectors";
import {ValidatorBeaconNodes} from "../../models/validatorBeaconNodes";
import {Eth2ApiClient} from "../../services/eth2/client/eth2ApiClient";
import {WinstonLogger} from "@chainsafe/lodestar-utils";

interface IValidatorServices {
[validatorAddress: string]: Validator;
Expand Down Expand Up @@ -153,19 +155,22 @@ function* loadValidatorStatusSaga(

function* startService(
action: ReturnType<typeof startNewValidatorService>,
): Generator<SelectEffect | PutEffect | Promise<void>, void, IValidatorBeaconNodes> {
const logger = new ValidatorLogger();
const validatorBeaconNodes = yield select(getBeaconNodes);
): Generator<SelectEffect | PutEffect | Promise<void>, void> {
const publicKey = action.payload.publicKey.toHex();
// TODO: Use beacon chain proxy instead of first node
const eth2API = validatorBeaconNodes[publicKey][0].client;
const eth2API = new Eth2ApiClient(config, "http://localhost:5052");
const slashingProtection = new SlashingProtection({
config,
controller: cgDbController,
});

console.log(eth2API, slashingProtection);

const logger = new WinstonLogger() as ValidatorLogger;

if (!validatorServices[publicKey]) {
validatorServices[publicKey] = new Validator({
slashingProtection: new SlashingProtection({
config,
controller: cgDbController,
}),
slashingProtection,
api: eth2API,
config,
secretKeys: [action.payload.privateKey],
Expand Down
46 changes: 46 additions & 0 deletions src/renderer/services/eth2/client/eth2ApiClient/beacon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {ICGEth2BeaconApi, ICGEth2BeaconApiState} from "../interface";
import {IBeaconBlocksApi, IBeaconPoolApi} from "@chainsafe/lodestar-validator/lib/api/interface/beacon";
import {BeaconBlock, BeaconState, Genesis} from "@chainsafe/lodestar-types";
import {HttpClient} from "../../../api";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {Json} from "@chainsafe/ssz";
import {BeaconBlocks} from "./beaconBlocks";
import {BeaconState as BeaconStateApi} from "./beaconState";
import {BeaconPool} from "./beaconPool";

export class Beacon implements ICGEth2BeaconApi {
public blocks: IBeaconBlocksApi;
public state: ICGEth2BeaconApiState;
public pool: IBeaconPoolApi;

private readonly httpClient: HttpClient;
private readonly config: IBeaconConfig;
// TODO: implement logger;
public constructor(config: IBeaconConfig, httpClient: HttpClient) {
this.config = config;
this.httpClient = httpClient;

this.blocks = new BeaconBlocks(config, httpClient);
this.state = new BeaconStateApi(config, httpClient);
this.pool = new BeaconPool(config, httpClient);
}

public getGenesis = async (): Promise<Genesis | null> => {
try {
const genesisResponse = await this.httpClient.get<{data: Json}>("/eth/v1/beacon/genesis");
return this.config.types.Genesis.fromJson(genesisResponse.data, {case: "snake"});
} catch (e) {
// TODO: implement logger;
console.error("Failed to obtain genesis time", {error: e.message});
return null;
}
};

public getChainHead = async (): Promise<BeaconBlock> => {
throw new Error("Method 'getChainHead' not implemented.");
};

public getBeaconState = async (): Promise<BeaconState> => {
throw new Error("Method 'getBeaconState' not implemented.");
};
}
20 changes: 20 additions & 0 deletions src/renderer/services/eth2/client/eth2ApiClient/beaconBlocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {IBeaconBlocksApi} from "@chainsafe/lodestar-validator/lib/api/interface/beacon";
import {HttpClient} from "../../../api";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {SignedBeaconBlock} from "@chainsafe/lodestar-types";

export class BeaconBlocks implements IBeaconBlocksApi {
private readonly httpClient: HttpClient;
private readonly config: IBeaconConfig;
public constructor(config: IBeaconConfig, httpClient: HttpClient) {
this.config = config;
this.httpClient = httpClient;
}

public publishBlock = async (block: SignedBeaconBlock): Promise<void> => {
await this.httpClient.post(
"/eth/v1/beacon/blocks",
this.config.types.SignedBeaconBlock.toJson(block, {case: "snake"}),
);
};
}
20 changes: 20 additions & 0 deletions src/renderer/services/eth2/client/eth2ApiClient/beaconPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {IBeaconPoolApi} from "@chainsafe/lodestar-validator/lib/api/interface/beacon";
import {HttpClient} from "../../../api";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {Attestation} from "@chainsafe/lodestar-types";

export class BeaconPool implements IBeaconPoolApi {
private readonly httpClient: HttpClient;
private readonly config: IBeaconConfig;
public constructor(config: IBeaconConfig, httpClient: HttpClient) {
this.config = config;
this.httpClient = httpClient;
}

public submitAttestation = async (attestation: Attestation): Promise<void> => {
await this.httpClient.post(
"/eth/v1/beacon/pool/attestations",
this.config.types.Attestation.toJson(attestation, {case: "snake"}),
);
};
}
78 changes: 78 additions & 0 deletions src/renderer/services/eth2/client/eth2ApiClient/beaconState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
BLSPubkey,
Fork,
SignedBeaconHeaderResponse,
ValidatorIndex,
ValidatorResponse,
} from "@chainsafe/lodestar-types";
import {ICGEth2BeaconApiState} from "../interface";
import {HttpClient} from "../../../api";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {Json} from "@chainsafe/ssz";

export class BeaconState implements ICGEth2BeaconApiState {
private readonly httpClient: HttpClient;
private readonly config: IBeaconConfig;
// TODO: implement logger;
public constructor(config: IBeaconConfig, httpClient: HttpClient) {
this.config = config;
this.httpClient = httpClient;
}

public getFork = async (stateId: "head"): Promise<Fork | null> => {
try {
const forkResponse = await this.httpClient.get<{data: Json}>(`/eth/v1/beacon/states/${stateId}/fork`);
return this.config.types.Fork.fromJson(forkResponse.data, {case: "snake"});
} catch (e) {
// TODO: implement logger;
console.error("Failed to fetch head fork version", {error: e.message});
return null;
}
};

public getStateValidator = async (
stateId: "head",
validatorId: ValidatorIndex | BLSPubkey,
): Promise<ValidatorResponse | null> => {
const id =
typeof validatorId === "number"
? validatorId.toString()
: this.config.types.BLSPubkey.toJson(validatorId)?.toString() ?? "";
try {
const url = `/eth/v1/beacon/states/${stateId}/validators/${id}`;
const stateValidatorResponse = await this.httpClient.get<{data: Json}>(url);
return this.config.types.ValidatorResponse.fromJson(stateValidatorResponse.data, {case: "snake"});
} catch (e) {
// TODO: implement logger;
console.error("Failed to fetch validator", {validatorId: id, error: e.message});
return null;
}
};

public getBlockHeader = async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
stateId: "head",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
blockId: "head" | number | string,
): Promise<SignedBeaconHeaderResponse> => {
throw new Error("Method 'getBlockHeader' not implemented.");
};

public getValidator = async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
stateId: "head",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
validatorId: string | BLSPubkey | ValidatorIndex,
): Promise<ValidatorResponse> => {
throw new Error("Method 'getValidator' not implemented.");
};

public getValidators = async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
stateId?: "head",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
validatorIds?: (string | ValidatorIndex)[],
): Promise<ValidatorResponse[]> => {
throw new Error("Method 'getValidators' not implemented.");
};
}
49 changes: 49 additions & 0 deletions src/renderer/services/eth2/client/eth2ApiClient/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {BeaconEvent, BeaconEventType, IEventsApi} from "@chainsafe/lodestar-validator/lib/api/interface/events";
import {IStoppableEventIterable, LodestarEventIterator} from "@chainsafe/lodestar-utils";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {ContainerType} from "@chainsafe/ssz";

export class Events implements IEventsApi {
private readonly baseUrl: string;
private readonly config: IBeaconConfig;
public constructor(config: IBeaconConfig, baseUrl: string) {
this.config = config;
this.baseUrl = baseUrl;
}

public getEventStream = (topics: BeaconEventType[]): IStoppableEventIterable<BeaconEvent> => {
const url = `${this.baseUrl}/eth/v1/events?${topics.map((topic) => `topics=${topic}`).join("&")}`;
const eventSource = new EventSource(url);
return new LodestarEventIterator(({push}): (() => void) => {
eventSource.onmessage = (event): void => {
if (topics.includes(event.type as BeaconEventType)) {
push(this.deserializeBeaconEventMessage(event));
}
};
return (): void => {
eventSource.close();
};
});
};

private deserializeBeaconEventMessage = (msg: MessageEvent): BeaconEvent => {
switch (msg.type) {
case BeaconEventType.BLOCK:
return {
type: BeaconEventType.BLOCK,
message: this.deserializeEventData(this.config.types.BlockEventPayload, msg.data),
};
case BeaconEventType.CHAIN_REORG:
return {
type: BeaconEventType.CHAIN_REORG,
message: this.deserializeEventData(this.config.types.ChainReorg, msg.data),
};
default:
throw new Error("Unsupported beacon event type " + msg.type);
}
};

private deserializeEventData = <T extends BeaconEvent["message"]>(type: ContainerType<T>, data: string): T => {
return type.fromJson(JSON.parse(data));
};
}
42 changes: 42 additions & 0 deletions src/renderer/services/eth2/client/eth2ApiClient/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {ICGEth2ValidatorApi, IValidatorBeaconClient, ICGEth2BeaconApi, ICGEth2NodeApi} from "../interface";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {IEventsApi} from "@chainsafe/lodestar-validator/lib/api/interface/events";
import {IEth2ChainHead} from "../../../../models/types/head";
import {Beacon} from "./beacon";
import {HttpClient} from "../../../api";
import {AbstractApiClient} from "@chainsafe/lodestar-validator/lib/api/abstract";
import {WinstonLogger} from "@chainsafe/lodestar-utils";
import {Validator} from "./validator";
import {NodeApi} from "./nodeApi";
import {Events} from "./events";

export class Eth2ApiClient extends AbstractApiClient implements IValidatorBeaconClient {
public validator: ICGEth2ValidatorApi;
public beacon: ICGEth2BeaconApi; //
public node: ICGEth2NodeApi;
public events: IEventsApi;

public url: string;

private readonly httpClient: HttpClient;
public constructor(config: IBeaconConfig, url: string) {
// TODO: logger: create new or get it from outside?
super(config, new WinstonLogger());
this.url = url;
this.httpClient = new HttpClient(url);

this.validator = new Validator(config, this.httpClient);
this.beacon = new Beacon(config, this.httpClient);
this.events = new Events(config, url);
this.node = new NodeApi(config, this.httpClient);
}

public getVersion = async (): Promise<string> => {
throw new Error("Method 'getVersion' not implemented.");
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public onNewChainHead = (callback: (head: IEth2ChainHead) => void): NodeJS.Timeout => {
throw new Error("Method 'onNewChainHead' not implemented.");
Copy link
Member

Choose a reason for hiding this comment

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

implement using events api

Copy link
Member

Choose a reason for hiding this comment

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

@BeroBurny I don't see this implemented anywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is not used by any part of the code, if will be required it will be implemented correctly

Copy link
Member

Choose a reason for hiding this comment

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

It's not used by validator but it's used by chainguardian. It's used to display beacon node current slot when syncing. You always used synced node so that part of code wasn't executed

};
}
24 changes: 24 additions & 0 deletions src/renderer/services/eth2/client/eth2ApiClient/nodeApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {ICGEth2NodeApi} from "../interface";
import {HttpClient} from "../../../api";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {SyncingStatus} from "@chainsafe/lodestar-types";
import {Json} from "@chainsafe/ssz";

export class NodeApi implements ICGEth2NodeApi {
private readonly httpClient: HttpClient;
private readonly config: IBeaconConfig;
public constructor(config: IBeaconConfig, httpClient: HttpClient) {
this.config = config;
this.httpClient = httpClient;
}

public getSyncingStatus = async (): Promise<SyncingStatus> => {
const syncingResponse = await this.httpClient.get<{data: Json}>("/eth/v1/node/syncing");
return this.config.types.SyncingStatus.fromJson(syncingResponse.data, {case: "snake"});
};

public getVersion = async (): Promise<string> => {
const versionResponse = await this.httpClient.get<{data: {version: string}}>("/eth/v1/node/version");
return versionResponse.data.version;
};
}
Loading