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

BuidlerEVM JSON-RPC Server #438

Merged
merged 19 commits into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
135cad9
Added simple JSON-RPC server task.
BencicAndrej Feb 6, 2020
85e9cbd
Updated body parsing and error handling logic.
BencicAndrej Feb 6, 2020
a9ab1af
Refactored JSON-RPC server into manageable components.
BencicAndrej Feb 7, 2020
66cfdc5
Addressed PR comments.
BencicAndrej Feb 7, 2020
457d18f
Addressed PR comments.
BencicAndrej Feb 7, 2020
3ddad21
Included reading the buffer inside of try/catch.
BencicAndrej Feb 7, 2020
31090f3
Refactored tests to support multiple providers under test.
BencicAndrej Feb 11, 2020
7708848
Fix json-rpc response and request's id types
alcuadrado Feb 11, 2020
fd539bf
Added check that --network parameter is not set when running `buidler…
BencicAndrej Feb 11, 2020
fb2c905
Merge remote-tracking branch 'origin/buidlerevm-rpc' into buidlerevm-rpc
BencicAndrej Feb 11, 2020
41404fe
Updated server shutdown method to include error handling.
BencicAndrej Feb 11, 2020
3b3e33a
Added explanation for empty throw.
BencicAndrej Feb 11, 2020
5e2bd43
Added specialized transaction error, and check for error codes.
BencicAndrej Feb 11, 2020
358cfa2
Added websocket support for JSON-RPC server.
BencicAndrej Feb 12, 2020
df1b070
Added eth_subscribe support for WebSocket server.
BencicAndrej Feb 12, 2020
057b4e0
Updated wording.
BencicAndrej Feb 14, 2020
e2d56f3
Changed how listeners work for eth_subscribe.
BencicAndrej Feb 14, 2020
2071817
Updated TransactionExecutionError to include parent and keep the stack.
BencicAndrej Feb 14, 2020
6355d03
Merge branch 'master' into buidlerevm-rpc
nebojsa94 Feb 14, 2020
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
3 changes: 2 additions & 1 deletion packages/buidler-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"time-require": "^0.1.2"
},
"dependencies": {
"@nomiclabs/ethereumjs-vm": "^4.1.1",
"@types/bn.js": "^4.11.5",
"@types/lru-cache": "^5.1.0",
"abort-controller": "^3.0.0",
Expand All @@ -86,7 +87,6 @@
"ethereumjs-common": "^1.3.2",
"ethereumjs-tx": "^2.1.1",
"ethereumjs-util": "^6.1.0",
"@nomiclabs/ethereumjs-vm": "^4.1.1",
"find-up": "^2.1.0",
"fp-ts": "1.19.3",
"fs-extra": "^7.0.1",
Expand All @@ -98,6 +98,7 @@
"mocha": "^5.2.0",
"node-fetch": "^2.6.0",
"qs": "^6.7.0",
"raw-body": "^2.4.1",
"semver": "^6.3.0",
"solc": "0.5.15",
"solidity-parser-antlr": "^0.4.2",
Expand Down
66 changes: 66 additions & 0 deletions packages/buidler-core/src/builtin-tasks/jsonrpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import debug from "debug";

import {
JsonRpcServer,
JsonRpcServerConfig
} from "../internal/buidler-evm/jsonrpc/server";
import { BUIDLEREVM_NETWORK_NAME } from "../internal/constants";
import { task } from "../internal/core/config/config-env";
import { BuidlerError } from "../internal/core/errors";
import { ERRORS } from "../internal/core/errors-list";
import { createProvider } from "../internal/core/providers/construction";
import { lazyObject } from "../internal/util/lazy";
import { EthereumProvider, ResolvedBuidlerConfig } from "../types";

import { TASK_JSONRPC } from "./task-names";

const log = debug("buidler:core:tasks:jsonrpc");

function _createBuidlerRuntimeEnvironment(
config: ResolvedBuidlerConfig
): EthereumProvider {
log("Creating BuidlerRuntimeEnvironment");

const networkName = BUIDLEREVM_NETWORK_NAME;
const networkConfig = config.networks[networkName];

return lazyObject(() => {
log(`Creating buidlerevm provider for JSON-RPC sever`);
return createProvider(
networkName,
networkConfig,
config.solc.version,
config.paths
);
});
}

export default function() {
task(TASK_JSONRPC, "Starts a buidler JSON-RPC server").setAction(
async (_, { config }) => {
try {
BencicAndrej marked this conversation as resolved.
Show resolved Hide resolved
const serverConfig: JsonRpcServerConfig = {
hostname: "localhost",
port: 8545,
BencicAndrej marked this conversation as resolved.
Show resolved Hide resolved
ethereum: _createBuidlerRuntimeEnvironment(config)
BencicAndrej marked this conversation as resolved.
Show resolved Hide resolved
};

const server = new JsonRpcServer(serverConfig);

process.exitCode = await server.listen();
} catch (error) {
if (BuidlerError.isBuidlerError(error)) {
BencicAndrej marked this conversation as resolved.
Show resolved Hide resolved
throw error;
}

throw new BuidlerError(
ERRORS.BUILTIN_TASKS.JSONRPC_SERVER_ERROR,
{
error: error.message
},
error
);
}
}
);
}
2 changes: 2 additions & 0 deletions packages/buidler-core/src/builtin-tasks/task-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const TASK_HELP = "help";

export const TASK_RUN = "run";

export const TASK_JSONRPC = "jsonrpc";

export const TASK_TEST = "test";

export const TASK_TEST_RUN_MOCHA_TESTS = "test:run-mocha-tests";
Expand Down
166 changes: 166 additions & 0 deletions packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import chalk from "chalk";
import debug from "debug";
import { IncomingMessage, ServerResponse } from "http";
import getRawBody from "raw-body";

import { EthereumProvider } from "../../../types";
import { BUIDLER_NAME } from "../../constants";
import { BuidlerError } from "../../core/errors";
import { ERRORS, getErrorCode } from "../../core/errors-list";
import {
isValidJsonRequest,
isValidJsonResponse,
JsonRpcRequest,
JsonRpcResponse
} from "../../core/providers/http";
import {
BuidlerEVMProviderError,
InternalError,
InvalidJsonInputError,
InvalidRequestError
} from "../provider/errors";

const log = debug("buidler:core:buidler-evm:jsonrpc");

export default class JsonRpcHandler {
private _ethereum: EthereumProvider;

constructor(ethereum: EthereumProvider) {
this._ethereum = ethereum;
}

public requestListener = async (
req: IncomingMessage,
res: ServerResponse
) => {
let rpcReq: JsonRpcRequest | undefined;
let rpcResp: JsonRpcResponse | undefined;

try {
rpcReq = await this._readRequest(req);

rpcResp = await this._handleRequest(rpcReq);
} catch (error) {
rpcResp = await this._handleError(error);
}

if (rpcReq !== undefined) {
rpcResp.id = rpcReq.id;
}

// Validate the RPC response.
if (!isValidJsonResponse(rpcResp)) {
rpcResp = await this._handleError(new InternalError("Internal error"));
}

res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(rpcResp));
};

private _readRequest = async (
req: IncomingMessage
): Promise<JsonRpcRequest> => {
const buf = await getRawBody(req);
const text = buf.toString();
BencicAndrej marked this conversation as resolved.
Show resolved Hide resolved
let json;

try {
json = JSON.parse(text);
} catch (error) {
throw new InvalidJsonInputError(`Parse error: ${error.message}`);
}

if (!isValidJsonRequest(json)) {
throw new InvalidRequestError("Invalid request");
}

return json;
};

private _handleRequest = async (
req: JsonRpcRequest
): Promise<JsonRpcResponse> => {
console.log(req.method);

const result = await this._ethereum.send(req.method, req.params);

return {
jsonrpc: "2.0",
id: req.id,
result
};
};

private _handleError = async (error: any): Promise<JsonRpcResponse> => {
this._printError(error);

const rpcResp: JsonRpcResponse = {
jsonrpc: "2.0",
error: {
code: error.code,
message: error.message
}
};

if (!(error instanceof BuidlerEVMProviderError)) {
rpcResp.error = new InternalError("Internal error");
}

return rpcResp;
};

private _printError = (error: any) => {
let showStackTraces = process.argv.includes("--show-stack-traces");
BencicAndrej marked this conversation as resolved.
Show resolved Hide resolved
let isBuidlerError = false;

if (error instanceof BuidlerEVMProviderError) {
const wrappedError = new BuidlerError(
ERRORS.BUILTIN_TASKS.JSONRPC_HANDLER_ERROR,
{
error: error.message
},
error
);

console.error(chalk.red(`Error ${wrappedError.message}`));
} else if (BuidlerError.isBuidlerError(error)) {
isBuidlerError = true;
console.error(chalk.red(`Error ${error.message}`));
} else if (error instanceof Error) {
console.error(
chalk.red(`An unexpected error occurred: ${error.message}`)
);
showStackTraces = true;
} else {
console.error(chalk.red("An unexpected error occurred."));
showStackTraces = true;
}

console.log("");

if (showStackTraces) {
console.error(error.stack);
} else {
if (!isBuidlerError) {
console.error(
`If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug`
);
}

if (BuidlerError.isBuidlerError(error)) {
const link = `https://buidler.dev/${getErrorCode(
error.errorDescriptor
)}`;

console.error(
`For more info go to ${link} or run ${BUIDLER_NAME} with --show-stack-traces`
);
} else {
console.error(
`For more info run ${BUIDLER_NAME} with --show-stack-traces`
);
}
}
};
}
46 changes: 46 additions & 0 deletions packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import debug from "debug";
import * as http from "http";

import { EthereumProvider } from "../../../types";

import JsonRpcHandler from "./handler";

const log = debug("buidler:core:buidler-evm:jsonrpc");

export interface JsonRpcServerConfig {
hostname: string;
port: number;

ethereum: EthereumProvider;
}

export class JsonRpcServer {
private _config: JsonRpcServerConfig;

constructor(config: JsonRpcServerConfig) {
this._config = config;
}

public listen = (): Promise<number> => {
return new Promise<number>((resolve, reject) => {
const { hostname, port, ethereum } = this._config;

log(`Starting JSON-RPC server on port ${port}`);

const handler = new JsonRpcHandler(ethereum);
const server = http.createServer(handler.requestListener);

process.once("SIGINT", async () => {
log(`Stopping JSON-RPC server`);

resolve(0);
});

process.once("uncaughtException", reject);
BencicAndrej marked this conversation as resolved.
Show resolved Hide resolved

server.listen(port, hostname, () => {
console.log(`Started JSON-RPC server at http://${hostname}:${port}/`);
});
});
};
}
12 changes: 12 additions & 0 deletions packages/buidler-core/src/internal/core/errors-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,18 @@ Please check Buidler's output for more details.`
description: `Buidler flatten doesn't support cyclic dependencies.

We recommend not using this kind of dependencies.`
},
JSONRPC_SERVER_ERROR: {
number: 604,
message: "Error running JSON-RPC server: %error%",
title: "Error running JSON-RPC server",
description: `There was error while starting the JSON-RPC HTTP server.`
},
JSONRPC_HANDLER_ERROR: {
number: 605,
message: "Error handling JSON-RPC request: %error%",
title: "Error handling JSON-RPC request",
description: `Handling an incoming JSON-RPC request resulted in an error.`
}
},
ARTIFACTS: {
Expand Down
Loading