Skip to content

Commit

Permalink
Merge pull request #28 from etherspot/redirect-eth-rpc-calls
Browse files Browse the repository at this point in the history
feat: redirect eth rpc calls
  • Loading branch information
0xSulpiride authored May 11, 2023
2 parents 3ebf621 + 343054c commit ef5041c
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 49 deletions.
39 changes: 8 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,7 @@
2. build `yarn build && yarn bootstrap`
3. `cp config.json.default config.json`
4. edit `config.json`
5. (optional) run local geth-node
```bash
docker run --rm -ti --name geth -p 8545:8545 ethereum/client-go:v1.10.26 \
--miner.gaslimit 12000000 \
--http --http.api personal,eth,net,web3,debug \
--http.vhosts '*,localhost,host.docker.internal' --http.addr "0.0.0.0" \
--ignore-legacy-receipts --allow-insecure-unlock --rpc.allow-unprotected-txs \
--dev \
--nodiscover --maxpeers 0 \
--networkid 1337
```
5. (optional) run local geth-node from `test/geth-dev`
6. run `./skandha`
7. Skandha will run for all chains available in `config.json`
8. Networks will be available at `http://localhost:14337/{chainId}/` (e.g. for dev `http://localhost:14337/1337/`)
Expand All @@ -44,28 +34,15 @@ docker run --rm -ti --name geth -p 8545:8545 ethereum/client-go:v1.10.26 \
4. `docker run --mount type=bind,source="$(pwd)"/config.json,target=/usr/app/config.json,readonly -dp 14337:14337 etherspot/skandha start`


### RPC Methods Checklist

- [x] eth_chainId
- [x] eth_supportedEntryPoints
- [x] eth_sendUserOperation
- [x] eth_estimateUserOperationGas
- [x] eth_getUserOperationReceipt
- [x] eth_getUserOperationByHash
- [x] web3_clientVersion
- [x] debug_bundler_clearState
- [x] debug_bundler_dumpMempool
- [x] debug_bundler_setReputation
- [x] debug_bundler_dumpReputation
- [x] debug_bundler_setBundlingMode
- [x] debug_bundler_setBundleInterval
- [x] debug_bundler_sendBundleNow

### Additional features
- [x] Unsafe mode - bypass opcode & stake validation (Enabled by --unsafeMode)
## Additional features
- [x] Unsafe mode - bypass opcode & stake validation
- [x] Redirect RPC - Redirect ETH rpc calls to the underlying execution client. This is needed if you use UserOp.js

### CLI Options
- `--unsafeMode` - enables unsafeMode
- `--redirectRpc` - enables redirecting eth rpc calls

### Relayer Configuration
## Relayer Configuration

#### Config.json

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "root",
"private": true,
"version": "0.0.2",
"version": "0.0.3",
"engines": {
"node": ">=12.9.0"
},
"scripts": {
"clean": "rm -rf ./packages/*/lib ./packages/*/*.tsbuildinfo",
"bootstrap": "lerna bootstrap & lerna link",
"build": "yarn workspace db run build & lerna run build",
"build": "yarn workspace types run build && yarn workspace db run build & lerna run build",
"lint": "eslint --color --ext .ts packages/*/src/",
"fix-lint": "eslint --ext .ts --fix packages/*/src/",
"test:unit": "lerna run test:unit --no-bail --concurrency 1",
Expand Down
21 changes: 19 additions & 2 deletions packages/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import RpcError from "types/lib/api/errors/rpc-error";
import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes";
import { FastifyInstance, RouteHandler } from "fastify";
import logger from "./logger";
import { BundlerRPCMethods, CustomRPCMethods } from "./constants";
import { EthAPI, DebugAPI, Web3API } from "./modules";
import {
BundlerRPCMethods,
CustomRPCMethods,
RedirectedRPCMethods,
} from "./constants";
import { EthAPI, DebugAPI, Web3API, RedirectAPI } from "./modules";
import { deepHexlify } from "./utils";

export interface RpcHandlerOptions {
Expand All @@ -21,6 +25,7 @@ export interface EtherspotBundlerOptions {
config: Config;
db: IDbController;
testingMode: boolean;
redirectRpc: boolean;
}

export interface RelayerAPI {
Expand All @@ -37,12 +42,14 @@ export class ApiApp {
private relayers: RelayerAPI[] = [];

private testingMode = false;
private redirectRpc = false;

constructor(options: EtherspotBundlerOptions) {
this.server = options.server;
this.config = options.config;
this.db = options.db;
this.testingMode = options.testingMode;
this.redirectRpc = options.redirectRpc;
this.setupRoutes();
}

Expand Down Expand Up @@ -74,6 +81,7 @@ export class ApiApp {
const ethApi = new EthAPI(relayer.eth);
const debugApi = new DebugAPI(relayer.debug);
const web3Api = new Web3API(relayer.web3);
const redirectApi = new RedirectAPI(network, this.config);

this.relayers.push({
relayer,
Expand Down Expand Up @@ -120,6 +128,15 @@ export class ApiApp {
}
}

if (this.redirectRpc && method in RedirectedRPCMethods) {
const body = await redirectApi.redirect(method, params);
return res.status(200).send({
jsonrpc,
id,
...body,
});
}

if (result === undefined) {
switch (method) {
case BundlerRPCMethods.eth_supportedEntryPoints:
Expand Down
39 changes: 39 additions & 0 deletions packages/api/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,42 @@ export const BundlerRPCMethods = {
debug_bundler_setBundleInterval: "debug_bundler_setBundleInterval",
debug_bundler_sendBundleNow: "debug_bundler_sendBundleNow",
};

export const RedirectedRPCMethods = {
web3_sha3: "web3_sha3",
net_version: "net_version",
net_listening: "net_listening",
net_peerCount: "net_peerCount",
eth_protocolVersion: "eth_protocolVersion",
eth_gasPrice: "eth_gasPrice",
eth_blockNumber: "eth_blockNumber",
eth_getBalance: "eth_getBalance",
eth_getStorageAt: "eth_getStorageAt",
eth_getTransactionCount: "eth_getTransactionCount",
eth_getBlockTransactionCountByHash: "eth_getBlockTransactionCountByHash",
eth_getBlockTransactionCountByNumber: "eth_getBlockTransactionCountByNumber",
eth_getUncleCountByBlockHash: "eth_getUncleCountByBlockHash",
eth_getUncleCountByBlockNumber: "eth_getUncleCountByBlockNumber",
eth_getCode: "eth_getCode",
eth_sign: "eth_sign",
eth_call: "eth_call",
eth_estimateGas: "eth_estimateGas",
eth_getBlockByHash: "eth_getBlockByHash",
eth_getBlockByNumber: "eth_getBlockByNumber",
eth_getTransactionByHash: "eth_getTransactionByHash",
eth_getTransactionByBlockHashAndIndex:
"eth_getTransactionByBlockHashAndIndex",
eth_getTransactionByBlockNumberAndIndex:
"eth_getTransactionByBlockNumberAndIndex",
eth_getTransactionReceipt: "eth_getTransactionReceipt",
eth_getUncleByBlockHashAndIndex: "eth_getUncleByBlockHashAndIndex",
eth_getUncleByBlockNumberAndIndex: "eth_getUncleByBlockNumberAndIndex",
eth_newFilter: "eth_newFilter",
eth_newBlockFilter: "eth_newBlockFilter",
eth_newPendingTransactionFilter: "eth_newPendingTransactionFilter",
eth_uninstallFilter: "eth_uninstallFilter",
eth_getFilterChanges: "eth_getFilterChanges",
eth_getFilterLogs: "eth_getFilterLogs",
eth_getLogs: "eth_getLogs",
eth_maxPriorityFeePerGas: "eth_maxPriorityFeePerGas",
};
1 change: 1 addition & 0 deletions packages/api/src/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./debug";
export * from "./web3";
export * from "./eth";
export * from "./redirect";
29 changes: 29 additions & 0 deletions packages/api/src/modules/redirect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { providers } from "ethers";
import { Config } from "executor/lib/config";
import { NetworkName } from "types/src";

export class RedirectAPI {
private provider: providers.JsonRpcProvider;

constructor(private network: NetworkName, private config: Config) {
this.provider = this.config.getNetworkProvider(
this.network
) as providers.JsonRpcProvider;
}

async redirect(method: string, params: any[]): Promise<any> {
return await this.provider
.send(method, params)
.then((result) => ({ result }))
.catch((err: any) => {
if (err.body) {
try {
const body = JSON.parse(err.body);
return body;
// eslint-disable-next-line no-empty
} catch (err) {}
}
throw err;
});
}
}
3 changes: 2 additions & 1 deletion packages/cli/src/cmds/start/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { IBundlerArgs } from "./index";
export async function bundlerHandler(
args: IBundlerArgs & IGlobalArgs
): Promise<void> {
const { dataDir, networksFile, testingMode, unsafeMode } = args;
const { dataDir, networksFile, testingMode, unsafeMode, redirectRpc } = args;

let config: Config;
try {
Expand Down Expand Up @@ -65,6 +65,7 @@ export async function bundlerHandler(
config: config,
db,
testingMode,
redirectRpc,
});

await server.listen();
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/cmds/start/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { bundlerHandler } from "./handler";
export interface IBundlerArgs {
testingMode: boolean;
unsafeMode: boolean;
redirectRpc: boolean;
}

export const bundlerOptions = {
Expand All @@ -20,6 +21,13 @@ export const bundlerOptions = {
default: false,
choices: [true, false],
},
redirectRpc: {
description:
"Redirect ETH-related rpc calls to the underlying execution client",
type: "boolean",
default: false,
choices: [true, false],
},
};

export const start: ICliCommand<IBundlerArgs, IGlobalArgs> = {
Expand Down
29 changes: 21 additions & 8 deletions packages/executor/src/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,18 @@ export class Eth {
preVerificationGas: 0,
verificationGasLimit: 10e6,
};
const preVerificationGas = this.calcPreVerificationGas(userOp);
userOpComplemented.preVerificationGas = preVerificationGas;

const { returnInfo } =
await this.userOpValidationService.simulateValidation(
await this.userOpValidationService.validateForEstimation(
userOpComplemented,
entryPoint
);

// eslint-disable-next-line prefer-const
let { preOpGas, validAfter, validUntil } = returnInfo;

const callGasLimit = await this.provider
.estimateGas({
from: entryPoint,
Expand All @@ -103,17 +110,22 @@ export class Eth {
err.message.match(/reason="(.*?)"/)?.at(1) ?? "execution reverted";
throw new RpcError(message, RpcErrorCodes.EXECUTION_REVERTED);
});
const preVerificationGas = this.calcPreVerificationGas(userOp);
const verificationGas = BigNumber.from(returnInfo.preOpGas).toNumber();
let deadline: any = undefined;
if (returnInfo.deadline) {
deadline = BigNumber.from(returnInfo.deadline);
// const preVerificationGas = this.calcPreVerificationGas(userOp);
const verificationGas = BigNumber.from(preOpGas).toNumber();
validAfter = BigNumber.from(validAfter);
validUntil = BigNumber.from(validUntil);
if (validUntil === BigNumber.from(0)) {
validUntil = undefined;
}
if (validAfter === BigNumber.from(0)) {
validAfter = undefined;
}
return {
preVerificationGas,
verificationGas,
validAfter,
validUntil,
callGasLimit,
deadline: deadline,
};
}

Expand Down Expand Up @@ -292,14 +304,15 @@ export class Eth {
} as any;

const packed = arrayify(packUserOp(p, false));
const lengthInWord = (packed.length + 31) / 32;
const callDataCost = packed
.map((x) => (x === 0 ? ov.zeroByte : ov.nonZeroByte))
.reduce((sum, x) => sum + x);
const ret = Math.round(
callDataCost +
ov.fixed / ov.bundleSize +
ov.perUserOp +
ov.perUserOpWord * packed.length
ov.perUserOpWord * lengthInWord
);
return ret;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/executor/src/modules/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ export class Web3 {
constructor(private config: Config) {}

clientVersion(): string {
return `skandha/${this.config.unsafeMode ? "unsafe/" : ""}0.0.1`; // TODO: get version based on commit hash
return `skandha/${this.config.unsafeMode ? "unsafe/" : ""}0.0.3`; // TODO: get version based on commit hash
}
}
24 changes: 24 additions & 0 deletions packages/executor/src/services/UserOpValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,30 @@ export class UserOpValidationService {
);
}

async validateForEstimation(
userOp: UserOperationStruct,
entryPoint: string
): Promise<any> {
const entryPointContract = EntryPoint__factory.connect(
entryPoint,
this.provider
);
const errorResult = await entryPointContract.callStatic
.simulateValidation(userOp, { gasLimit: 10e6 })
.catch((e: any) => e);
if (errorResult.errorName === "FailedOp") {
throw new RpcError(
errorResult.errorArgs.at(-1),
RpcErrorCodes.VALIDATION_FAILED
);
}
if (errorResult.errorName !== "ValidationResult") {
throw errorResult;
}

return errorResult.errorArgs;
}

async simulateValidation(
userOp: UserOperationStruct,
entryPoint: string,
Expand Down
5 changes: 3 additions & 2 deletions packages/types/src/api/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { UserOperationStruct } from "../executor/contracts/EntryPoint";

export type EstimatedUserOperationGas = {
preVerificationGas: BigNumberish;
callGasLimit: BigNumberish;
verificationGas: BigNumberish;
deadline?: BigNumberish;
callGasLimit: BigNumberish;
validAfter?: BigNumberish;
validUntil?: BigNumberish;
};

export type UserOperationByHashResponse = {
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@
ajv-formats "^2.1.1"
fast-uri "^2.0.0"

"@fastify/cors@^8.2.1":
"@fastify/[email protected]":
version "8.2.1"
resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-8.2.1.tgz#dd348162bcbfb87dff4b492e2bef32d41244006a"
integrity sha512-2H2MrDD3ea7g707g1CNNLWb9/tYbmw7HS+MK2SDcgjxwzbOFR93JortelTIO8DBFsZqFtEpKNxiZfSyrGgYcbw==
Expand Down Expand Up @@ -2146,7 +2146,7 @@
dependencies:
"@types/express" "*"

"@types/connect@*", "@types/connect@^3.4.35":
"@types/connect@*", "@types/[email protected]":
version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
Expand Down

0 comments on commit ef5041c

Please sign in to comment.