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

Greg/validator/rpc #185

Merged
merged 12 commits into from
May 5, 2019
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"rules": {
"@typescript-eslint/indent": ["error", 2],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": ["warn", {
"varsIgnorePattern": "^_"
Expand Down
4 changes: 2 additions & 2 deletions src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {BeaconChain} from "../chain";
import {OpPool} from "../opPool";
import {JSONRPC} from "../rpc/protocol";
import {WSServer} from "../rpc/transport";
import {BeaconAPI} from "../rpc/api";
import {BeaconApi} from "../rpc/api";

interface Service {
start(): Promise<void>;
Expand Down Expand Up @@ -65,7 +65,7 @@ class BeaconNode {
});
this.rpc = new JSONRPC(this.conf.rpc, {
transport: new WSServer(this.conf.rpc),
api: new BeaconAPI(this.conf.rpc, {
api: new BeaconApi(this.conf.rpc, {
chain: this.chain,
db: this.db,
opPool: this.opPool,
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./interface";
export * from "./interfaces";
export * from "./mock";
export * from "./api";
export * from "./modules";
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions src/rpc/api/interface.ts → src/rpc/api/interfaces/beacon.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {Attestation, AttestationData, BeaconBlock, bytes32, Deposit, Shard, Slot, Eth1Data} from "../../types";
import {Attestation, AttestationData, BeaconBlock, bytes32, Deposit, Shard, Slot, Eth1Data} from "../../../types";

/**
* The API interface defines the calls that can be made externally
*/
export interface API {
export interface IBeaconApi {
/**
* Return the current chain head
*/
Expand Down
7 changes: 7 additions & 0 deletions src/rpc/api/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {IBeaconApi} from "./beacon";
import {IValidatorApi} from "./validator";

export {
IBeaconApi,
IValidatorApi
};
70 changes: 70 additions & 0 deletions src/rpc/api/interfaces/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* The API interface defines the calls that can be made from a Validator
*/
import {
BeaconBlock, bytes, bytes32, bytes48, Fork, IndexedAttestation, Shard, Slot, SyncingStatus, uint64,
ValidatorDuty
} from "../../../types";

export interface IValidatorApi {
/**
* Requests that the BeaconNode identify information about its implementation in a format similar to a HTTP User-Agent field.
* @returns {Promise<bytes32>} An ASCII-encoded hex string which uniquely defines the implementation of the BeaconNode and its current software version.
*/
getClientVersion(): Promise<bytes32>;

/**
* Requests the BeaconNode to provide which fork version it is currently on.
* @returns {Promise<{fork: Fork; chainId: uint64}>}
*/
getFork(): Promise<{fork: Fork; chainId: uint64}>;

/**
* Requests the genesis_time parameter from the BeaconNode, which should be consistent across all BeaconNodes that follow the same beacon chain.
* @returns {Promise<uint64>} The genesis_time, which is a fairly static configuration option for the BeaconNode.
*/
getGenesisTime(): Promise<uint64>;

/**
* Requests the BeaconNode to describe if it's currently syncing or not, and if it is, what block it is up to. This is modelled after the Eth1.0 JSON-RPC eth_syncing call.
* @returns {Promise<boolean | SyncingStatus>} Either false if the node is not syncing, or a SyncingStatus object if it is.
*/
getSyncingStatus(): Promise<boolean | SyncingStatus>;

/**
* Requests the BeaconNode to provide a set of “duties”, which are actions that should be performed by ValidatorClients. This API call should be polled at every slot, to ensure that any chain reorganisations are catered for, and to ensure that the currently connected BeaconNode is properly synchronised.
* @param {bytes48[]} validatorPubkeys
* @returns {Promise<{currentVersion: bytes4; validatorDuties: ValidatorDuty[]}>} A list of unique validator public keys, where each item is a 0x encoded hex string.
*/
getDuties(validatorPubkeys: bytes48[]): Promise<{currentVersion: Fork; validatorDuties: ValidatorDuty[]}>;
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved

/**
* Requests a BeaconNode to produce a valid block, which can then be signed by a ValidatorClient.
* @param {Slot} slot
* @param {bytes} randaoReveal
* @returns {Promise<BeaconBlock>} A proposed BeaconBlock object, but with the signature field left blank.
*/
produceBlock(slot: Slot, randaoReveal: bytes): Promise<BeaconBlock>;

/**
* Requests that the BeaconNode produce an IndexedAttestation, with a blank signature field, which the ValidatorClient will then sign.
* @param {Slot} slot
* @param {Shard} shard
* @returns {Promise<IndexedAttestation>}
*/
produceAttestation(slot: Slot, shard: Shard): Promise<IndexedAttestation>;

/**
* Instructs the BeaconNode to publish a newly signed beacon block to the beacon network, to be included in the beacon chain.
* @param {BeaconBlock} beaconBlock
* @returns {Promise<void>}
*/
publishBlock(beaconBlock: BeaconBlock): Promise<void>;

/**
* Instructs the BeaconNode to publish a newly signed IndexedAttestation object, to be incorporated into the beacon chain.
* @param {IndexedAttestation} indexedAttestation
* @returns {Promise<void>}
*/
publishAttestation(indexedAttestation: IndexedAttestation): Promise<void>;
}
4 changes: 2 additions & 2 deletions src/rpc/api/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Attestation, AttestationData, BeaconBlock, bytes32, Deposit, Shard, Slot

import {getEmptyBlock} from "../../chain/genesis";

import {API} from "./interface";
import {IBeaconApi} from "./interfaces";

export interface MockAPIOpts {
head?: BeaconBlock;
Expand All @@ -12,7 +12,7 @@ export interface MockAPIOpts {
attestationData?: AttestationData;
}

export class MockAPI implements API {
export class MockAPI implements IBeaconApi {
private head;
private attestations;
public constructor(opts?: MockAPIOpts) {
Expand Down
12 changes: 6 additions & 6 deletions src/rpc/api/api.ts → src/rpc/api/modules/beacon.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {Attestation, AttestationData, BeaconBlock, bytes32, Deposit, Shard, Slot, Eth1Data} from "../../types";
import {DB} from "../../db";
import {BeaconChain} from "../../chain";
import {OpPool} from "../../opPool";
import {Attestation, AttestationData, BeaconBlock, bytes32, Deposit, Shard, Slot, Eth1Data} from "../../../types";
import {DB} from "../../../db";
import {BeaconChain} from "../../../chain";
import {OpPool} from "../../../opPool";

import {API} from "./interface";
import {IBeaconApi} from "../interfaces";

export class BeaconAPI implements API {
export class BeaconApi implements IBeaconApi {
private chain: BeaconChain;
private db: DB;
private opPool: OpPool;
Expand Down
7 changes: 7 additions & 0 deletions src/rpc/api/modules/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {ValidatorApi} from "./validator";
import {BeaconApi} from "./beacon";

export {
ValidatorApi,
BeaconApi
};
59 changes: 59 additions & 0 deletions src/rpc/api/modules/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Attestation, AttestationData, BeaconBlock, bytes32, Deposit, Shard, Slot, Eth1Data, uint64,
Fork, SyncingStatus, ValidatorDuty, bytes48, bytes, IndexedAttestation
} from "../../../types";
import {DB} from "../../../db";
import {BeaconChain} from "../../../chain";
import {OpPool} from "../../../opPool";

import {IValidatorApi} from "../interfaces";

export class ValidatorApi implements IValidatorApi {
private chain: BeaconChain;
private db: DB;
private opPool: OpPool;

public constructor(opts, {chain, db, opPool}) {
this.chain = chain;
this.db = db;
this.opPool = opPool;
}

public async getClientVersion(): Promise<bytes32> {
return Buffer.alloc(32);
}

public async getFork(): Promise<{fork: Fork; chainId: uint64}> {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {} as {fork: Fork; chainId: uint64};
}

public async getGenesisTime(): Promise<uint64> {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {} as uint64;
}

public async getSyncingStatus(): Promise<boolean | SyncingStatus> {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {} as boolean | SyncingStatus;
}

public async getDuties(validatorPubkeys: bytes48[]): Promise<{currentVersion: Fork; validatorDuties: ValidatorDuty[]}> {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {} as {currentVersion: Fork; validatorDuties: ValidatorDuty[]};
}

public async produceBlock(slot: Slot, randaoReveal: bytes): Promise<BeaconBlock> {
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {} as BeaconBlock;
}

public async produceAttestation(slot: Slot, shard: Shard): Promise<IndexedAttestation> {
Copy link
Member

Choose a reason for hiding this comment

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

I think we want to return an AttestationData, see getAttestationData of the BeaconApi

Copy link
Member Author

Choose a reason for hiding this comment

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

Are you sure? IndexedAttestation contains AttestationData

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, now I see the issue, opPool.receiveAttestaion looks for AttestationData

Copy link
Member

Choose a reason for hiding this comment

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

I'm looking at https://github.com/ethereum/eth2.0-specs/blob/dev/specs/validator/0_beacon-chain-validator.md#attestations-1

It seems like the validator is supposed to get an AttestationData and turn it into an Attestation, and then publish that.

Copy link
Member Author

Choose a reason for hiding this comment

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

hmmm ok I see what you are saying -- So the beacon chain should send the validator attestationData and then the validator turns it into an IndexedAttestation
?

// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {} as IndexedAttestation;
}

public async publishBlock(beaconBlock: BeaconBlock): Promise<void> {}

public async publishAttestation(indexedAttestation: IndexedAttestation): Promise<void> {}
}
7 changes: 5 additions & 2 deletions src/rpc/protocol/jsonRpc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as jsonRpc from "noice-json-rpc";


import {API} from "../api";
import {IValidatorApi, IBeaconApi} from "../api";

export interface LikeSocketServer extends jsonRpc.LikeSocketServer {
start(): Promise<void>;
Expand All @@ -18,23 +18,26 @@ export class JSONRPC {
private transport: LikeSocketServer;
private jsonRpcApi;

public constructor(opts, {transport, api}: {transport: LikeSocketServer; api: API}) {
public constructor(opts, {transport, api}: {transport: LikeSocketServer; api: IBeaconApi | IValidatorApi}) {
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
this.transport = transport;
// attach the json-rpc server to underlying transport
this.rpcServer = new jsonRpc.Server(this.transport);
this.jsonRpcApi = this.rpcServer.api();
// collect the api methods into an enumerable object for rpc exposure
const methods = {};
for (let name of Object.getOwnPropertyNames(Object.getPrototypeOf(api))) {
console.log(api[name]);
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
if (name !== 'constructor' && typeof api[name] === 'function') {
methods[name] = api[name].bind(api);
}
}
this.jsonRpcApi.BeaconChain.expose(methods);
}

public async start(): Promise<void> {
await this.transport.start();
}

public async stop(): Promise<void> {
await this.transport.stop();
}
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export * from "./misc";
export * from "./operations";
export * from "./block";
export * from "./state";
export * from "./validator";
4 changes: 2 additions & 2 deletions src/types/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ export interface HistoricalBatch {
export const HistoricalBatch: SimpleContainerType = {
name: "HistoricalBatch",
fields: [
["blockRoots", [bytes32, SLOTS_PER_HISTORICAL_ROOT]],
["stateRoots", [bytes32, SLOTS_PER_HISTORICAL_ROOT]],
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
["blockRoots", [bytes32]],
["stateRoots", [bytes32]],
],
};

42 changes: 42 additions & 0 deletions src/types/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {bytes48, Shard, Slot, uint64} from "./primitive";
import {SimpleContainerType} from "@chainsafe/ssz";

export interface ValidatorDuty {
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
// The validator's public key, uniquely identifying them
validatorPubkey: bytes48;
// The index of the validator in the committee
committeeIndex: uint64;
// The slot at which the validator must attest
attestationSlot: Slot;
// The shard in which the validator must attest
attestationShard: Shard;
// The slot in which a validator must propose a block, this field can be Null
blockProductionSlot: Slot;
}
export const ValidatorDuty: SimpleContainerType = {
name: "ValidatorDuty",
fields: [
["validatorPubkey", bytes48],
["committeeIndex", uint64],
["attestationSlot", Slot],
["attestationShard", Shard],
["blockProductionSlot", Slot],
],
};

export interface SyncingStatus {
// The block at which syncing started (will only be reset, after the sync reached his head)
startingBlock: uint64;
// Current Block
currentBlock: uint64;
// The estimated highest block, or current target block number
highestBlock: uint64;
}
export const SyncingStatus: SimpleContainerType = {
name: "SyncingStatus",
fields: [
["startingBlock", uint64],
["currentBlock", uint64],
["highestBlock", uint64],
],
};
2 changes: 1 addition & 1 deletion test/unit/rpc/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import {BeaconChain} from "../../../src/chain";
import {OpPool} from "../../../src/opPool";
import {LevelDB} from "../../../src/db";
import {BeaconAPI} from "../../../src/rpc";
// import {BeaconAPI} from "../../../src/rpc";

import { generateEmptyBlock } from "../../utils/block";
import { generateEmptyAttestation } from "../../utils/attestation";
Expand Down
8 changes: 4 additions & 4 deletions test/unit/rpc/jsonRpcOverHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe("Json RPC over http", () => {
logger.silent(true);
const rpcServer = new HttpServer({port: 32421});
server = rpcServer.server;
rpc = new JSONRPC({}, {transport: rpcServer, api: new MockAPI()})
rpc = new JSONRPC({}, {transport: rpcServer, api: new MockAPI()});
await rpc.start();
});
after(async () => {
Expand All @@ -33,7 +33,7 @@ describe("Json RPC over http", () => {
}
done();
});
})
});
it("should fail for unknown methods", (done) => {
request.default(server)
.post('/')
Expand All @@ -49,7 +49,7 @@ describe("Json RPC over http", () => {
assert.fail('Should not be successfull');
done();
});
})
});
it("should fail for methods other than POST", (done) => {
request.default(server)
.get('/')
Expand All @@ -58,7 +58,7 @@ describe("Json RPC over http", () => {
.end((err) => {
done(err);
});
})
});
it("should fail to start on existing port", (done) => {
const rpc = new JSONRPC({}, {transport: new HttpServer({port: 32421}), api: new MockAPI()});
rpc.start()
Expand Down
4 changes: 2 additions & 2 deletions test/unit/rpc/jsonRpcOverWs.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { assert } from "chai";
import * as jsonRpc from "noice-json-rpc";
import Websocket from "ws";
import {MockAPI, JSONRPC, API, WSServer} from "../../../src/rpc";
import {MockAPI, JSONRPC, BeaconApi, WSServer} from "../../../src/rpc";
import { generateEmptyBlock } from "../../utils/block";
import { generateEmptyAttestation } from "../../utils/attestation";

describe("Json RPC over WS", () => {
const rpc = new JSONRPC({}, {transport: new WSServer({port: 32420}), api: new MockAPI()});
let client;
let ws;
let clientApi: {BeaconChain: API};
let clientApi: {BeaconChain: BeaconApi};
before(async () => {
await rpc.start();
ws = new Websocket("ws://localhost:32420");
Expand Down