Skip to content

Commit

Permalink
feat(rpc): <- adds first implementation of this
Browse files Browse the repository at this point in the history
  • Loading branch information
allemanfredi committed Sep 25, 2024
1 parent d34e8c4 commit 1e3258b
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 270 deletions.
2 changes: 2 additions & 0 deletions packages/rpc/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PORT=
JSON_RPC_URL_1=
2 changes: 2 additions & 0 deletions packages/rpc/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/dist
**/node_modules
21 changes: 21 additions & 0 deletions packages/rpc/.eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
extends:
- "eslint:recommended"
- "plugin:@typescript-eslint/eslint-recommended"
- "plugin:@typescript-eslint/recommended"
- "prettier"
parser: "@typescript-eslint/parser"
parserOptions:
project: "tsconfig.json"
plugins:
- "@typescript-eslint"
root: true
rules:
"@typescript-eslint/no-floating-promises":
- error
- ignoreIIFE: true
ignoreVoid: true
"@typescript-eslint/no-inferrable-types": "off"
"@typescript-eslint/no-unused-vars":
- error
- argsIgnorePattern: "_"
varsIgnorePattern: "_"
2 changes: 2 additions & 0 deletions packages/rpc/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/dist
**/node_modules
7 changes: 7 additions & 0 deletions packages/rpc/.prettierrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
bracketSpacing: true
printWidth: 120
proseWrap: "always"
singleQuote: false
tabWidth: 2
trailingComma: "all"
semi: false
5 changes: 5 additions & 0 deletions packages/rpc/nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"watch": ["src"],
"ext": "ts,json",
"exec": "ts-node ./src/index.ts"
}
40 changes: 40 additions & 0 deletions packages/rpc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@gnosis/hashi-rpc",
"private": true,
"version": "0.1.0",
"scripts": {
"compile": "tsc",
"lint": "eslint --ignore-path ./.eslintignore --ext .js,.ts .",
"prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"",
"prettier:write": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\"",
"start": "node --loader ts-node/esm ./src/index.ts",
"start:prod": "node ./dist/index.js",
"start:dev": "nodemon"
},
"dependencies": {
"@ethereumjs/block": "^5.3.0",
"@ethereumjs/common": "^4.4.0",
"@ethereumjs/rlp": "^5.0.2",
"@ethereumjs/util": "^9.0.3",
"@gnosis/hashi-common": "0.1.0",
"body-parser": "^1.20.2",
"dotenv": "^16.4.5",
"ethers": "^6.13.2",
"express": "^4.19.2",
"json-rpc-2.0": "^1.7.0",
"kzg-wasm": "^0.4.0"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.0",
"@types/express": "^4.17.21",
"@types/node": "^20.8.9",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"eslint": "^8.51.0",
"eslint-config-prettier": "^9.0.0",
"nodemon": "^3.0.1",
"prettier": "^3.3.2",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
}
}
34 changes: 34 additions & 0 deletions packages/rpc/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import "dotenv/config"
import express from "express"
import bodyParser from "body-parser"
import { JSONRPCServer, TypedJSONRPCServer } from "json-rpc-2.0"
import { logger } from "@gnosis/hashi-common"

import logMiddleware from "./middlewares/log"
import getAccountAndStorageProof from "./methods/get-account-and-storage-proof"
import { Methods } from "./methods/types"

const start = async () => {
const server: TypedJSONRPCServer<Methods> = new JSONRPCServer()
server.addMethod("hashi_getAccountAndStorageProof", getAccountAndStorageProof)
server.applyMiddleware(logMiddleware)

const app = express()
app.use(bodyParser.json())

app.post("/v1", (_req, _res) => {
const jsonRPCRequest = _req.body
server.receive(jsonRPCRequest).then((_jsonRPCResponse) => {
if (_jsonRPCResponse) {
_res.json(_jsonRPCResponse)
} else {
_res.sendStatus(204)
}
})
})

app.listen(process.env.PORT)
logger.info(`Server listening on port ${process.env.PORT} ...`)
}

start()
66 changes: 66 additions & 0 deletions packages/rpc/src/methods/get-account-and-storage-proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import "dotenv/config"
import { ethers } from "ethers"
import { logger } from "@gnosis/hashi-common"
import { Block } from "@ethereumjs/block"
import { loadKZG } from "kzg-wasm"
import { Common, Chain, Hardfork } from "@ethereumjs/common"
import { RLP } from "@ethereumjs/rlp"
import { bigIntToHex, bytesToHex } from "@ethereumjs/util"

import {
AccountProof,
StorageProof,
GetAccountAndStorageProofParams,
GetAccountAndStorageProofResponse,
} from "../types"

const getAccountAndStorageProof = async ({
chainId,
address,
storageKeys,
blockNumber,
}: GetAccountAndStorageProofParams) => {
try {
const rpcUrl = process.env[`JSON_RPC_URL_${chainId}`]
if (!rpcUrl) throw new Error("Chain not supported")

const kzg = await loadKZG()
const common = new Common({
chain: Chain.Mainnet,
hardfork: Hardfork.Cancun,
customCrypto: {
kzg,
},
})

const provider = new ethers.JsonRpcProvider(rpcUrl)

const [proof, block] = await Promise.all([
provider.send("eth_getProof", [address, storageKeys, bigIntToHex(BigInt(blockNumber))]),
Block.fromJsonRpcProvider(provider, BigInt(blockNumber), { common }),
])

return {
accountProof: [
address,
bytesToHex(RLP.encode(proof.accountProof.map((_sibling: string) => RLP.decode(_sibling)))),
bytesToHex(block.header.serialize()),
chainId,
] as AccountProof,
storageProof: [
proof.storageHash,
proof.storageProof.map(({ key }: any) => key),
proof.storageProof.map(({ proof: storageProof }: any) =>
bytesToHex(RLP.encode(storageProof.map((_sibling: string) => RLP.decode(_sibling)))),
),
bytesToHex(block.header.serialize()),
chainId,
] as StorageProof,
} as GetAccountAndStorageProofResponse
} catch (_err) {
logger.error(_err)
throw _err
}
}

export default getAccountAndStorageProof
5 changes: 5 additions & 0 deletions packages/rpc/src/methods/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { GetAccountAndStorageProofParams, GetAccountAndStorageProofResponse } from "../types"

export type Methods = {
hashi_getAccountAndStorageProof(params: GetAccountAndStorageProofParams): GetAccountAndStorageProofResponse
}
12 changes: 12 additions & 0 deletions packages/rpc/src/middlewares/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { JSONRPCRequest, JSONRPCResponse, JSONRPCServerMiddlewareNext } from "json-rpc-2.0"
import { logger } from "@gnosis/hashi-common"

const logMiddleware = (_next: JSONRPCServerMiddlewareNext<any>, _request: JSONRPCRequest, _params: any) => {
logger.info(`Received ${JSON.stringify(_request)}`)
return _next(_request, _params).then((_response: JSONRPCResponse | null) => {
logger.info(`Responding ${JSON.stringify(_response)}`)
return _response
})
}

export default logMiddleware
15 changes: 15 additions & 0 deletions packages/rpc/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type AccountProof = [`0x${string}`, `0x${string}`, `0x${string}`, number]

export type StorageProof = [`0x${string}`, `0x${string}`[], `0x${string}`[], `0x${string}`, number]

export type GetAccountAndStorageProofParams = {
chainId: number
address: `0x${string}`
storageKeys: `0x${string}`[]
blockNumber: number
}

export type GetAccountAndStorageProofResponse = {
accountProof: AccountProof
storageProof: StorageProof
}
6 changes: 6 additions & 0 deletions packages/rpc/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
}
}
Loading

0 comments on commit 1e3258b

Please sign in to comment.