From 78d192f53f10b01f76fc9edaa15bf0caee6a255d Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Wed, 5 Jul 2023 22:33:44 -0400 Subject: [PATCH] refactor: move to new sdks --- .presetterrc.json | 9 ++ build.js | 50 ------- package.json | 51 +++---- patches/@chainsafe+bls+7.1.1.patch | 76 ++++++++++ polyfill.js | 5 - src-build/build.ts | 218 ----------------------------- src/client/client.ts | 49 +++---- src/client/constants.ts | 2 +- src/client/interfaces.ts | 2 +- src/client/prover.ts | 4 +- src/client/rpc/constants.ts | 8 +- src/client/rpc/errors.ts | 2 +- src/client/rpc/provider.ts | 65 +++++---- src/client/rpc/rpc.ts | 8 +- src/client/rpc/utils.ts | 2 +- src/client/rpc/validation.ts | 14 +- src/client/ssz.ts | 80 ++++++++++- src/client/utils.ts | 27 +--- src/index.ts | 12 +- tsconfig.build.json | 14 -- tsconfig.json | 13 -- 21 files changed, 272 insertions(+), 439 deletions(-) create mode 100644 .presetterrc.json delete mode 100644 build.js create mode 100644 patches/@chainsafe+bls+7.1.1.patch delete mode 100644 polyfill.js delete mode 100644 src-build/build.ts delete mode 100644 tsconfig.build.json delete mode 100644 tsconfig.json diff --git a/.presetterrc.json b/.presetterrc.json new file mode 100644 index 0000000..0eef0e5 --- /dev/null +++ b/.presetterrc.json @@ -0,0 +1,9 @@ +{ + "preset": [ + "@lumeweb/presetter-kernel-module-preset" + ], + "config": { + "official": true, + "browser": true + } +} diff --git a/build.js b/build.js deleted file mode 100644 index ac519aa..0000000 --- a/build.js +++ /dev/null @@ -1,50 +0,0 @@ -import esbuild from "esbuild"; -import { readFile } from "fs/promises"; -import path from "path"; - -await esbuild.build({ - entryPoints: ["src/index.ts"], - outfile: "dist/index.js", - format: "iife", - bundle: true, - legalComments: "external", - define: { - global: "self", - "import.meta": "true", - }, - plugins: [ - { - name: "base64", - setup(build) { - build.onResolve({ filter: /\?base64$/ }, (args) => { - return { - path: args.path, - pluginData: { - isAbsolute: path.isAbsolute(args.path), - resolveDir: args.resolveDir, - }, - namespace: "base64-loader", - }; - }); - build.onLoad( - { filter: /\?base64$/, namespace: "base64-loader" }, - async (args) => { - const fullPath = args.pluginData.isAbsolute - ? args.path - : path.join(args.pluginData.resolveDir, args.path); - return { - contents: Buffer.from( - await readFile(fullPath.replace(/\?base64$/, "")) - ).toString("base64"), - loader: "text", - }; - } - ); - }, - }, - ], - external: ["fs"], - inject: ["./polyfill.js"], -}); - -export {}; diff --git a/package.json b/package.json index 5f13021..5fc0f25 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,27 @@ "version": "0.1.0", "license": "MIT", "type": "module", + "readme": "ERROR: No README data found!", + "_id": "@lumeweb/kernel-eth@0.1.0", + "repository": { + "type": "git", + "url": "gitea@git.lumeweb.com:LumeWeb/kernel-eth.git" + }, "scripts": { - "build-script": "tsc --project tsconfig.build.json && mv dist-build/build.js dist-build/build.mjs", - "compile": "npm run build-script && rimraf node_modules/@lumeweb/dht-rpc-client/node_modules node_modules/@lumeweb/kernel-dht-client/node_modules/libkmodule && node build.js", - "build": "npm run compile && node ./dist-build/build.mjs dev" + "prepare": "presetter bootstrap", + "build": "shx touch nop.js;run build", + "semantic-release": "semantic-release" + }, + "devDependencies": { + "@lumeweb/presetter-kernel-module-preset": "^0.1.0-develop.30", + "@rollup/plugin-alias": "^5.0.0", + "patch-package": "^7.0.0", + "rollup-plugin-ignore-import": "^1.3.2" }, "dependencies": { "@chainsafe/as-sha256": "0.4.1", - "@chainsafe/bls": "git+https://git.lumeweb.com/LumeWeb/chainsafe-bls.git", - "@chainsafe/blst": "^0.2.8", + "@chainsafe/bls": "^7.1.1", + "@chainsafe/blst": "0.2.9", "@chainsafe/ssz": "0.11.1", "@ethereumjs/block": "^4.2.1", "@ethereumjs/blockchain": "^6.2.1", @@ -25,40 +37,17 @@ "@lodestar/light-client": "^1.7.2", "@lodestar/params": "^1.8.0", "@lodestar/types": "^1.7.2", - "@lumeweb/kernel-rpc-client": "git+https://git.lumeweb.com/LumeWeb/kernel-rpc-client.git", + "@lumeweb/kernel-rpc-client": "^0.0.2-develop.1", + "@lumeweb/libkernel": "^0.1.0-develop.14", + "@lumeweb/libweb": "^0.2.0-develop.21", "decimal.js": "^10.4.3", "ethers": "^6.3.0", "json-rpc-2.0": "^1.5.1", - "libkmodule": "^0.2.53", "lodash": "^4.17.21", - "path-browserify": "^1.0.1", "rlp": "^3.0.0", - "stream-browserify": "^3.0.0", "web3-core": "^1.9.0", "web3-core-method": "^1.9.0", "web3-eth": "^1.9.0", "yaml": "^2.2.1" - }, - "devDependencies": { - "@lumeweb/interface-relay": "git+https://git.lumeweb.com/LumeWeb/interface-relay.git", - "@scure/bip39": "^1.2.0", - "@skynetlabs/skynet-nodejs": "^2.9.0", - "@types/lodash": "^4.14.194", - "@types/node": "^18.15.11", - "@types/read": "^0.0.29", - "buffer": "^6.0.3", - "cli-progress": "^3.12.0", - "crypto-browserify": "^3.12.0", - "esbuild": "^0.17.17", - "esbuild-plugin-wasm": "^1.0.0", - "prettier": "^2.8.7", - "process": "^0.11.10", - "read": "^2.1.0", - "typescript": "^5.0.4" - }, - "browser": { - "crypto": "crypto-browserify", - "stream": "stream-browserify", - "path": "path-browserify" } } diff --git a/patches/@chainsafe+bls+7.1.1.patch b/patches/@chainsafe+bls+7.1.1.patch new file mode 100644 index 0000000..93926fd --- /dev/null +++ b/patches/@chainsafe+bls+7.1.1.patch @@ -0,0 +1,76 @@ +diff --git a/node_modules/@chainsafe/bls/lib/herumi/index.js b/node_modules/@chainsafe/bls/lib/herumi/index.js +index 4ed8fdd..f979984 100644 +--- a/node_modules/@chainsafe/bls/lib/herumi/index.js ++++ b/node_modules/@chainsafe/bls/lib/herumi/index.js +@@ -1,16 +1,20 @@ +-import { SecretKey } from "./secretKey.js"; +-import { PublicKey } from "./publicKey.js"; +-import { Signature } from "./signature.js"; +-import { init, destroy } from "./context.js"; +-import { functionalInterfaceFactory } from "../functional.js"; +-await init(); +-export * from "../constants.js"; +-export { SecretKey, PublicKey, Signature, init, destroy }; +-export const bls = { +- implementation: "herumi", +- SecretKey, +- PublicKey, +- Signature, +- ...functionalInterfaceFactory({ SecretKey, PublicKey, Signature }), +-}; +-export default bls; ++import { SecretKey } from './secretKey.js' ++import { PublicKey } from './publicKey.js' ++import { Signature } from './signature.js' ++import { init, destroy } from './context.js' ++import { functionalInterfaceFactory } from '../functional.js' ++ ++export * from '../constants.js' ++export { SecretKey, PublicKey, Signature, init, destroy } ++ ++export const bls = async () => { ++ await init() ++ return { ++ implementation: 'herumi', ++ SecretKey, ++ PublicKey, ++ Signature, ++ ...functionalInterfaceFactory({ SecretKey, PublicKey, Signature }), ++ } ++} ++export default bls +diff --git a/node_modules/@chainsafe/bls/lib/index.d.ts b/node_modules/@chainsafe/bls/lib/index.d.ts +index 35a9432..097a938 100644 +--- a/node_modules/@chainsafe/bls/lib/index.d.ts ++++ b/node_modules/@chainsafe/bls/lib/index.d.ts +@@ -1,3 +1,3 @@ + import type { IBls } from "./types.js"; +-declare let bls: IBls; ++export declare const bls: () => Promise; + export default bls; +diff --git a/node_modules/@chainsafe/bls/lib/index.js b/node_modules/@chainsafe/bls/lib/index.js +index c2a5bdf..9572018 100644 +--- a/node_modules/@chainsafe/bls/lib/index.js ++++ b/node_modules/@chainsafe/bls/lib/index.js +@@ -1,11 +1,14 @@ + import { getImplementation } from "./getImplementation.js"; + // Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js + const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]"; +-let bls; +-try { +- bls = await getImplementation(isNode ? "blst-native" : "herumi"); +-} +-catch (e) { +- bls = await getImplementation("herumi"); +-} ++export const bls = async () => { ++ let bls; ++ try { ++ bls = await getImplementation(isNode ? "blst-native" : "herumi"); ++ } ++ catch (e) { ++ bls = await getImplementation("herumi"); ++ } ++ return bls; ++}; + export default bls; diff --git a/polyfill.js b/polyfill.js deleted file mode 100644 index 2598b27..0000000 --- a/polyfill.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as process from "process"; -import { Buffer } from "buffer"; - -self.process = process; -self.Buffer = Buffer; diff --git a/src-build/build.ts b/src-build/build.ts deleted file mode 100644 index c0833e3..0000000 --- a/src-build/build.ts +++ /dev/null @@ -1,218 +0,0 @@ -// This is the standard build script for a kernel module. - -import * as fs from "fs"; -import read from "read"; -import * as bip39 from "@scure/bip39"; -import { wordlist } from "@scure/bip39/wordlists/english.js"; -//@ts-ignore -import { SkynetClient } from "@skynetlabs/skynet-nodejs"; - -// Helper variables to make it easier to return empty values alongside errors. -const nu8 = new Uint8Array(0); -const nkp = { - publicKey: nu8, - secretKey: nu8, -}; - -// readFile is a wrapper for fs.readFileSync that handles the try-catch for the -// caller. -function readFile(fileName: string): [string, string | null] { - try { - let data = fs.readFileSync(fileName, "utf8"); - return [data, null]; - } catch (err) { - return ["", "unable to read file: " + JSON.stringify(err)]; - } -} - -// readFileBinary is a wrapper for fs.readFileSync that handles the try-catch -// for the caller. -function readFileBinary(fileName: string): [Uint8Array, string | null] { - try { - let data = fs.readFileSync(fileName, null); - return [data, null]; - } catch (err) { - return [nu8, "unable to read file: " + JSON.stringify(err)]; - } -} - -// writeFile is a wrapper for fs.writeFileSync which handles the try-catch in a -// non-exception way. -function writeFile(fileName: string, fileData: string): string | null { - try { - fs.writeFileSync(fileName, fileData); - return null; - } catch (err) { - return "unable to write file: " + JSON.stringify(err); - } -} - -// handlePass handles all portions of the script that occur after the password -// has been requested. If no password needs to be requested, handlePass will be -// called with a null input. We need to structure the code this way because the -// password reader is async and we can only access the password when using a -// callback. -function handlePass(password: string) { - try { - // If we are running prod and the seed file does not exist, we - // need to confirm the password and also warn the user to use a - // secure password. - if (!fs.existsSync(seedFile) && process.argv[2] === "prod") { - // The file does not exist, we need to confirm the - // password. - console.log(); - console.log( - "No production entry found for module. Creating new production module..." - ); - console.log( - "If someone can guess the password, they can push arbitrary changes to your module." - ); - console.log("Please use a secure password."); - console.log(); - read( - { prompt: "Confirm Password: ", silent: true }, - function (err: any, confirmPassword: string) { - if (err) { - console.error("unable to fetch password:", err); - process.exit(1); - } - if (password !== confirmPassword) { - console.error("passwords do not match"); - process.exit(1); - } - handlePassConfirm(moduleSalt, password); - } - ); - } else { - // If the seed file does exist, or if we are using dev, - // there's no need to confirm the password but we do - // need to pass the logic off to the handlePassConfirm - // callback. - handlePassConfirm(moduleSalt, password); - } - } catch (err) { - console.error("Unable to read seedFile:", err); - process.exit(1); - } -} - -// handlePassConfirm handles the full script after the confirmation password -// has been provided. If not confirmation password is needed, this function -// will be called anyway using the unconfirmed password as input. -function handlePassConfirm(seed: string, password: string) { - // Create the seedFile if it does not exist. For dev we just save the - // seed to disk outright, because this is a dev build and therefore not - // security sensitive. Also the dev seed does not get pushed to the - // github repo. - // - // For prod, we use the seed to create a new seed (called the shield) - // which allows us to verify that the developer has provided the right - // password when deploying the module. The shield does get pushed to - // the github repo so that the production module is the same on all - // devices. - if (!fs.existsSync(seedFile) && process.argv[2] !== "prod") { - // Generate the seed phrase and write it to the file. - let seedPhrase = bip39.generateMnemonic(wordlist); - let errWF = writeFile(seedFile, seedPhrase); - if (errWF !== null) { - console.error("unable to write file:", errWF); - process.exit(1); - } - } else if (!fs.existsSync(seedFile) && process.argv[2] === "prod") { - // Generate the seed phrase. - let seedPhrase = bip39.generateMnemonic(wordlist); - // Write the registry link to the file. - } - - // Load or verify the seed. If this is prod, the password is used to - // create and verify the seed. If this is dev, we just load the seed - // with no password. - let seedPhrase: string; - let registryLink: string; - if (process.argv[2] === "prod") { - // Generate the seed phrase from the password. - seedPhrase = bip39.generateMnemonic(wordlist); - } else { - let [sp, errRF] = readFile(seedFile); - if (errRF !== null) { - console.error("unable to read seed phrase for dev command from disk"); - process.exit(1); - } - seedPhrase = sp; - } - - let metadata = { - Filename: "index.js", - }; - const client = new SkynetClient("https://web3portal.com"); - client - .uploadFile("dist/index.js") - .then((result: any) => { - console.log("Immutable Link for module:", result); - }) - .catch((err: any) => { - console.error("unable to upload file", err); - process.exit(1); - }); -} - -// Add a newline for readability. -console.log(); - -// Check for a 'dev' or 'prod' input to the script. -if (process.argv.length !== 3) { - console.error("need to provide either 'dev' or 'prod' as an input"); - process.exit(1); -} - -// Create the build folder if it does not exist. -if (!fs.existsSync("build")) { - fs.mkdirSync("build"); -} - -// Determine the seed file. -let seedFile: string; -if (process.argv[2] === "prod") { - seedFile = "module-skylink"; -} else if (process.argv[2] === "dev") { - seedFile = "build/dev-seed"; -} else { - console.error("need to provide either 'dev' or 'prod' as an input"); - process.exit(1); -} - -// If doing a prod deployment, check whether the salt file exists. If it does -// not, create it. -let moduleSalt: string; -if (!fs.existsSync(".module-salt")) { - moduleSalt = bip39.generateMnemonic(wordlist); - let errWF = writeFile(".module-salt", moduleSalt); - if (errWF !== null) { - console.error("unable to write module salt file:", errWF); - process.exit(1); - } -} else { - let [ms, errRF] = readFile(".module-salt"); - if (errRF !== null) { - console.error("unable to read moduleSalt"); - process.exit(1); - } - ms = ms.replace(/\n$/, ""); - moduleSalt = ms; -} - -// Need to get a password if this is a prod build. -if (process.argv[2] === "prod") { - read( - { prompt: "Password: ", silent: true }, - function (err: any, password: string) { - if (err) { - console.error("unable to fetch password:", err); - process.exit(1); - } - handlePass(password); - } - ); -} else { - handlePass(""); -} diff --git a/src/client/client.ts b/src/client/client.ts index c2b333a..5252c32 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -23,7 +23,8 @@ import { import { assertValidSignedHeader } from "@lodestar/light-client/validation"; import { SyncCommitteeFast } from "@lodestar/light-client"; import bls, { init } from "@chainsafe/bls/switchable"; -import { PublicKey } from "@chainsafe/bls/types.js"; +// @ts-ignore +import type { PublicKey } from "@chainsafe/bls/lib/types.js"; import { fromHexString } from "@chainsafe/ssz"; import * as capella from "@lodestar/types/capella"; import * as phase0 from "@lodestar/types/phase0"; @@ -42,7 +43,7 @@ export default class Client { latestBlockHash?: string; private config: ClientConfig = getDefaultClientConfig(); private genesisCommittee: Uint8Array[] = this.config.genesis.committee.map( - (pk) => fromHexString(pk) + (pk) => fromHexString(pk), ); private genesisPeriod = computeSyncPeriodAtSlot(this.config.genesis.slot); private genesisTime = this.config.genesis.time; @@ -76,11 +77,11 @@ export default class Client { const provider = new VerifyingProvider( this.rpcCallback, blockNumber, - blockhash + blockhash, ); this.subscribe((ei) => { console.log( - `Recieved a new blockheader: ${ei.blockNumber} ${ei.blockhash}` + `Recieved a new blockheader: ${ei.blockNumber} ${ei.blockhash}`, ); provider.update(ei.blockhash, ei.blockNumber); }); @@ -94,7 +95,7 @@ export default class Client { public getCurrentPeriod(): number { return computeSyncPeriodAtSlot( - getCurrentSlot(this.config.chainConfig, this.genesisTime) + getCurrentSlot(this.config.chainConfig, this.genesisTime), ); } @@ -115,12 +116,12 @@ export default class Client { async optimisticUpdateVerify( committee: Uint8Array[], - update: OptimisticUpdate + update: OptimisticUpdate, ): Promise { try { const { attestedHeader: header, syncAggregate } = update; const headerBlockRoot = phase0.ssz.BeaconBlockHeader.hashTreeRoot( - header.beacon + header.beacon, ); const committeeFast = this.deserializeSyncCommittee(committee); try { @@ -129,7 +130,7 @@ export default class Client { committeeFast, syncAggregate, headerBlockRoot, - header.beacon.slot + header.beacon.slot, ); } catch (e) { return { correct: false, reason: "invalid signatures" }; @@ -154,27 +155,27 @@ export default class Client { private isValidLightClientHeader( config: ChainForkConfig, - header: allForks.LightClientHeader + header: allForks.LightClientHeader, ): boolean { return isValidMerkleBranch( config .getExecutionForkTypes(header.beacon.slot) .ExecutionPayloadHeader.hashTreeRoot( - (header as capella.LightClientHeader).execution + (header as capella.LightClientHeader).execution, ), (header as capella.LightClientHeader).executionBranch, EXECUTION_PAYLOAD_DEPTH, EXECUTION_PAYLOAD_INDEX, - header.beacon.bodyRoot + header.beacon.bodyRoot, ); } public async getNextValidExecutionInfo( - retry: number = 10 + retry: number = 10, ): Promise { if (retry === 0) throw new Error( - "no valid execution payload found in the given retry limit" + "no valid execution payload found in the given retry limit", ); const ei = await this.getLatestExecution(); if (ei) return ei; @@ -201,7 +202,7 @@ export default class Client { let startPeriod = this.genesisPeriod; let lastCommitteeHash: Uint8Array = this.getCommitteeHash( - this.genesisCommittee + this.genesisCommittee, ); for (let period = startPeriod + 1; period <= currentPeriod; period++) { @@ -209,11 +210,11 @@ export default class Client { lastCommitteeHash = await this.prover.getCommitteeHash( period, currentPeriod, - DEFAULT_BATCH_SIZE + DEFAULT_BATCH_SIZE, ); } catch (e: any) { throw new Error( - `failed to fetch committee hash for prover at period(${period}): ${e.meessage}` + `failed to fetch committee hash for prover at period(${period}): ${e.meessage}`, ); } } @@ -225,7 +226,7 @@ export default class Client { let startPeriod = this.latestPeriod; let lastCommitteeHash: Uint8Array = this.getCommitteeHash( - this.latestCommittee as Uint8Array[] + this.latestCommittee as Uint8Array[], ); for (let period = startPeriod + 1; period <= currentPeriod; period++) { @@ -233,11 +234,11 @@ export default class Client { lastCommitteeHash = await this.prover.getCommitteeHash( period, currentPeriod, - DEFAULT_BATCH_SIZE + DEFAULT_BATCH_SIZE, ); } catch (e: any) { throw new Error( - `failed to fetch committee hash for prover at period(${period}): ${e.meessage}` + `failed to fetch committee hash for prover at period(${period}): ${e.meessage}`, ); } } @@ -246,7 +247,7 @@ export default class Client { async getCommittee( period: number, - expectedCommitteeHash: Uint8Array | null + expectedCommitteeHash: Uint8Array | null, ): Promise { if (period === this.genesisPeriod) return this.genesisCommittee; if (!expectedCommitteeHash) @@ -260,19 +261,19 @@ export default class Client { private async getLatestExecution(): Promise { const updateJSON = await this.prover.callback( - "consensus_optimistic_update" + "consensus_optimistic_update", ); const update = this.optimisticUpdateFromJSON(updateJSON); const verify = await this.optimisticUpdateVerify( this.latestCommittee as Uint8Array[], - update + update, ); if (!verify.correct) { console.error(`Invalid Optimistic Update: ${verify.reason}`); return null; } console.log( - `Optimistic update verified for slot ${updateJSON.attested_header.beacon.slot}` + `Optimistic update verified for slot ${updateJSON.attested_header.beacon.slot}`, ); return { blockhash: updateJSON.attested_header.execution.block_hash, @@ -281,7 +282,7 @@ export default class Client { } private deserializeSyncCommittee( - syncCommittee: Uint8Array[] + syncCommittee: Uint8Array[], ): SyncCommitteeFast { const pubkeys = this.deserializePubkeys(syncCommittee); return { diff --git a/src/client/constants.ts b/src/client/constants.ts index d6aebba..12101b2 100644 --- a/src/client/constants.ts +++ b/src/client/constants.ts @@ -522,7 +522,7 @@ export const mainnetConfig = { }; export const BEACON_SYNC_SUPER_MAJORITY = Math.ceil( - (BEACON_SYNC_COMMITTEE_SIZE * 2) / 3 + (BEACON_SYNC_COMMITTEE_SIZE * 2) / 3, ); // These are the rough numbers from benchmark experiments diff --git a/src/client/interfaces.ts b/src/client/interfaces.ts index 6c0917a..940f5f2 100644 --- a/src/client/interfaces.ts +++ b/src/client/interfaces.ts @@ -8,7 +8,7 @@ export interface IProver { getCommitteeHash( period: number, currentPeriod: number, - count: number + count: number, ): Promise; getSyncUpdate(period: number): Promise; diff --git a/src/client/prover.ts b/src/client/prover.ts index 3d89d3f..61884ef 100644 --- a/src/client/prover.ts +++ b/src/client/prover.ts @@ -24,7 +24,7 @@ export default class Prover implements IProver { async getSyncUpdate(period: number): Promise { const res = await this.callback("consensus_committee_period", { period }); return LightClientUpdateSSZ.deserialize( - Uint8Array.from(Object.values(res)) + Uint8Array.from(Object.values(res)), ); } @@ -39,7 +39,7 @@ export default class Prover implements IProver { async getCommitteeHash( period: number, currentPeriod: number, - cacheCount: number + cacheCount: number, ): Promise { const _count = Math.min(currentPeriod - period + 1, cacheCount); if (!this.cachedHashes.has(period)) { diff --git a/src/client/rpc/constants.ts b/src/client/rpc/constants.ts index ffe954d..d79f5f7 100644 --- a/src/client/rpc/constants.ts +++ b/src/client/rpc/constants.ts @@ -1,10 +1,10 @@ -export const ZERO_ADDR = '0x0000000000000000000000000000000000000000'; +export const ZERO_ADDR = "0x0000000000000000000000000000000000000000"; // TODO: set the correct gas limit! -export const GAS_LIMIT = '0x1c9c380'; +export const GAS_LIMIT = "0x1c9c380"; export const REQUEST_BATCH_SIZE = 10; export const MAX_SOCKET = 10; export const EMPTY_ACCOUNT_EXTCODEHASH = - '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; + "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; export const MAX_BLOCK_HISTORY = BigInt(256); export const MAX_BLOCK_FUTURE = BigInt(3); -export const DEFAULT_BLOCK_PARAMETER = 'latest'; +export const DEFAULT_BLOCK_PARAMETER = "latest"; diff --git a/src/client/rpc/errors.ts b/src/client/rpc/errors.ts index 350ad0e..648426e 100644 --- a/src/client/rpc/errors.ts +++ b/src/client/rpc/errors.ts @@ -1,4 +1,4 @@ -import { JSONRPCErrorCode, JSONRPCErrorException } from 'json-rpc-2.0'; +import { JSONRPCErrorCode, JSONRPCErrorException } from "json-rpc-2.0"; export class InternalError extends JSONRPCErrorException { constructor(message: string) { diff --git a/src/client/rpc/provider.ts b/src/client/rpc/provider.ts index 783d9a7..d84e4a3 100644 --- a/src/client/rpc/provider.ts +++ b/src/client/rpc/provider.ts @@ -35,7 +35,10 @@ import { MAX_BLOCK_FUTURE, DEFAULT_BLOCK_PARAMETER, } from "./constants.js"; -import { headerDataFromWeb3Response, blockDataFromWeb3Response } from "./utils"; +import { + headerDataFromWeb3Response, + blockDataFromWeb3Response, +} from "./utils.js"; import { keccak256 } from "ethers"; import { InternalError, InvalidParamsError } from "./errors.js"; @@ -68,14 +71,14 @@ export class VerifyingProvider { eth_estimateGas: this.estimateGas, eth_sendRawTransaction: this.sendRawTransaction, eth_getTransactionReceipt: this.getTransactionReceipt, - }) + }), ); constructor( rpcCallback: Function, blockNumber: bigint | number, blockHash: Bytes32, - chain: bigint | Chain = Chain.Mainnet + chain: bigint | Chain = Chain.Mainnet, ) { this.rpc = new RPC(rpcCallback); this.common = new Common({ @@ -93,7 +96,7 @@ export class VerifyingProvider { this.blockHashes[blockNumberHex] !== blockHash ) { console.log( - "Overriding an existing verified blockhash. Possibly the chain had a reorg" + "Overriding an existing verified blockhash. Possibly the chain had a reorg", ); } const latestBlockNumber = this.latestBlockNumber; @@ -119,7 +122,7 @@ export class VerifyingProvider { private async getBalance( addressHex: AddressHex, - blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER + blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER, ) { const header = await this.getBlockHeader(blockOpt); const address = Address.fromString(addressHex); @@ -134,7 +137,7 @@ export class VerifyingProvider { address, [], header.stateRoot, - proof + proof, ); if (!isAccountCorrect) { throw new InternalError("Invalid account proof provided by the RPC"); @@ -153,7 +156,7 @@ export class VerifyingProvider { private async getCode( addressHex: AddressHex, - blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER + blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER, ): Promise { const header = await this.getBlockHeader(blockOpt); const res = await this.rpc.requestBatch([ @@ -177,7 +180,7 @@ export class VerifyingProvider { address, [], header.stateRoot, - accountProof + accountProof, ); if (!isAccountCorrect) { throw new InternalError(`invalid account proof provided by the RPC`); @@ -185,11 +188,11 @@ export class VerifyingProvider { const isCodeCorrect = await this.verifyCodeHash( code, - accountProof.codeHash + accountProof.codeHash, ); if (!isCodeCorrect) { throw new InternalError( - `code provided by the RPC doesn't match the account's codeHash` + `code provided by the RPC doesn't match the account's codeHash`, ); } @@ -198,7 +201,7 @@ export class VerifyingProvider { private async getTransactionCount( addressHex: AddressHex, - blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER + blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER, ): Promise { const header = await this.getBlockHeader(blockOpt); const address = Address.fromString(addressHex); @@ -214,7 +217,7 @@ export class VerifyingProvider { address, [], header.stateRoot, - proof + proof, ); if (!isAccountCorrect) { throw new InternalError(`invalid account proof provided by the RPC`); @@ -225,7 +228,7 @@ export class VerifyingProvider { private async call( transaction: RPCTx, - blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER + blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER, ) { try { this.validateTx(transaction); @@ -265,7 +268,7 @@ export class VerifyingProvider { private async estimateGas( transaction: RPCTx, - blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER + blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER, ) { try { this.validateTx(transaction); @@ -284,7 +287,7 @@ export class VerifyingProvider { ? 2 : transaction.accessList ? 1 - : 0 + : 0, ); if (txType == BigInt(2)) { transaction.maxFeePerGas = @@ -350,7 +353,7 @@ export class VerifyingProvider { } private async getTransactionReceipt( - txHash: Bytes32 + txHash: Bytes32, ): Promise { const { result: receipt, success } = await this.rpc.request({ method: "eth_getTransactionReceipt", @@ -362,7 +365,7 @@ export class VerifyingProvider { const header = await this.getBlockHeader(receipt.blockNumber); const block = await this.getBlock(header); const index = block.transactions.findIndex( - (tx) => bufferToHex(tx.hash()) === txHash.toLowerCase() + (tx) => bufferToHex(tx.hash()) === txHash.toLowerCase(), ); if (index === -1) { throw new InternalError("the recipt provided by the RPC is invalid"); @@ -453,7 +456,7 @@ export class VerifyingProvider { } const responses = _.chunk( rawResponse.map((r: any) => r.result), - 2 + 2, ) as [AccountResponse, CodeResponse][]; for (let i = 0; i < accessList.length; i++) { @@ -471,7 +474,7 @@ export class VerifyingProvider { address, storageKeys, header.stateRoot, - accountProof + accountProof, ); if (!isAccountCorrect) { throw new InternalError(`invalid account proof provided by the RPC`); @@ -480,7 +483,7 @@ export class VerifyingProvider { const isCodeCorrect = await this.verifyCodeHash(code, codeHash); if (!isCodeCorrect) { throw new InternalError( - `code provided by the RPC doesn't match the account's codeHash` + `code provided by the RPC doesn't match the account's codeHash`, ); } @@ -496,7 +499,7 @@ export class VerifyingProvider { await vm.stateManager.putContractStorage( address, setLengthLeft(toBuffer(storageAccess.key), 32), - setLengthLeft(toBuffer(storageAccess.value), 32) + setLengthLeft(toBuffer(storageAccess.value), 32), ); } @@ -527,7 +530,7 @@ export class VerifyingProvider { throw new InvalidParamsError("specified block is too far in future"); } else if (blockNumber + MAX_BLOCK_HISTORY < this.latestBlockNumber) { throw new InvalidParamsError( - `specified block cannot older that ${MAX_BLOCK_HISTORY}` + `specified block cannot older that ${MAX_BLOCK_HISTORY}`, ); } return blockNumber; @@ -566,7 +569,7 @@ export class VerifyingProvider { if (!header.hash().equals(toBuffer(blockHash))) { throw new InternalError( - `blockhash doesn't match the blockInfo provided by the RPC` + `blockhash doesn't match the blockInfo provided by the RPC`, ); } this.blockHeaders[blockHash] = header; @@ -578,14 +581,14 @@ export class VerifyingProvider { address: Address, storageKeys: Bytes32[], stateRoot: Buffer, - proof: GetProof + proof: GetProof, ): Promise { const trie = new Trie(); const key = keccak256(address.toString()); const expectedAccountRLP = await trie.verifyProof( stateRoot, toBuffer(key), - proof.accountProof.map((a) => toBuffer(a)) + proof.accountProof.map((a) => toBuffer(a)), ); const account = Account.fromAccountData({ nonce: BigInt(proof.nonce), @@ -607,12 +610,12 @@ export class VerifyingProvider { for (let i = 0; i < storageKeys.length; i++) { const sp = proof.storageProof[i]; const key = keccak256( - bufferToHex(setLengthLeft(toBuffer(storageKeys[i]), 32)) + bufferToHex(setLengthLeft(toBuffer(storageKeys[i]), 32)), ); const expectedStorageRLP = await trie.verifyProof( toBuffer(proof.storageHash), toBuffer(key), - sp.proof.map((a) => toBuffer(a)) + sp.proof.map((a) => toBuffer(a)), ); const isStorageValid = (!expectedStorageRLP && sp.value === "0x0") || @@ -647,7 +650,7 @@ export class VerifyingProvider { BigInt(tx.maxPriorityFeePerGas) > BigInt(tx.maxFeePerGas) ) { throw new Error( - `maxPriorityFeePerGas (${tx.maxPriorityFeePerGas.toString()}) is bigger than maxFeePerGas (${tx.maxFeePerGas.toString()})` + `maxPriorityFeePerGas (${tx.maxPriorityFeePerGas.toString()}) is bigger than maxFeePerGas (${tx.maxFeePerGas.toString()})`, ); } } @@ -667,13 +670,13 @@ export class VerifyingProvider { if (!block.header.hash().equals(header.hash())) { throw new InternalError( - `BN(${header.number}): blockhash doest match the blockData provided by the RPC` + `BN(${header.number}): blockhash doest match the blockData provided by the RPC`, ); } if (!(await block.validateTransactionsTrie())) { throw new InternalError( - `transactionTree doesn't match the transactions provided by the RPC` + `transactionTree doesn't match the transactions provided by the RPC`, ); } @@ -696,7 +699,7 @@ export class VerifyingProvider { this.blockHashes[parentBlockNumberHex] !== parentBlockHash ) { console.log( - "Overriding an existing verified blockhash. Possibly the chain had a reorg" + "Overriding an existing verified blockhash. Possibly the chain had a reorg", ); } this.blockHashes[parentBlockNumberHex] = parentBlockHash; diff --git a/src/client/rpc/rpc.ts b/src/client/rpc/rpc.ts index 355e618..61b261c 100644 --- a/src/client/rpc/rpc.ts +++ b/src/client/rpc/rpc.ts @@ -25,7 +25,7 @@ export class RPC { } async requestBatch(requests: RPCRequest[]) { - const res = []; + const res: RPCResponse[] = []; for (const request of requests) { const r = await this._retryRequest(request); res.push(r); @@ -35,7 +35,7 @@ export class RPC { private async _retryRequest( _request: RPCRequest, - retry = 5 + retry = 5, ): Promise { const request = { ..._request, @@ -52,8 +52,8 @@ export class RPC { `RPC batch request failed after maximum retries: ${JSON.stringify( request, null, - 2 - )} ${JSON.stringify(res, null, 2)}` + 2, + )} ${JSON.stringify(res, null, 2)}`, ); } } diff --git a/src/client/rpc/utils.ts b/src/client/rpc/utils.ts index a18f632..19fead0 100644 --- a/src/client/rpc/utils.ts +++ b/src/client/rpc/utils.ts @@ -40,7 +40,7 @@ export function headerDataFromWeb3Response(blockInfo: any): HeaderData { } export function txDataFromWeb3Response( - txInfo: any + txInfo: any, ): TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData { return { ...txInfo, diff --git a/src/client/rpc/validation.ts b/src/client/rpc/validation.ts index 9b43892..d03afa7 100644 --- a/src/client/rpc/validation.ts +++ b/src/client/rpc/validation.ts @@ -1,4 +1,4 @@ -import { InvalidParamsError } from './errors'; +import { InvalidParamsError } from "./errors.js"; // Most of the validations are taken from: // https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/client/lib/rpc/validation.ts @@ -30,13 +30,13 @@ export const validators = { * @param index index of parameter */ hex(params: any[], index: number) { - if (typeof params[index] !== 'string') { + if (typeof params[index] !== "string") { throw new InvalidParamsError( `invalid argument ${index}: argument must be a hex string`, ); } - if (params[index].substr(0, 2) !== '0x') { + if (params[index].substr(0, 2) !== "0x") { throw new InvalidParamsError( `invalid argument ${index}: hex string without 0x prefix`, ); @@ -67,14 +67,14 @@ export const validators = { blockOption(params: any[], index: number) { const blockOption = params[index]; - if (typeof blockOption !== 'string') { + if (typeof blockOption !== "string") { throw new InvalidParamsError( `invalid argument ${index}: argument must be a string`, ); } try { - if (['latest', 'earliest', 'pending'].includes(blockOption)) { + if (["latest", "earliest", "pending"].includes(blockOption)) { return; } return this.hex([blockOption], 0); @@ -91,7 +91,7 @@ export const validators = { * @param index index of parameter */ bool(params: any[], index: number) { - if (typeof params[index] !== 'boolean') { + if (typeof params[index] !== "boolean") { throw new InvalidParamsError( `invalid argument ${index}: argument is not boolean`, ); @@ -118,7 +118,7 @@ export const validators = { transaction(params: any[], index: number) { const tx = params[index]; - if (typeof tx !== 'object') { + if (typeof tx !== "object") { throw new InvalidParamsError( `invalid argument ${index}: argument must be an object`, ); diff --git a/src/client/ssz.ts b/src/client/ssz.ts index ad36d64..f7a4493 100644 --- a/src/client/ssz.ts +++ b/src/client/ssz.ts @@ -1,5 +1,6 @@ import { ByteVectorType, + ContainerType, ListCompositeType, VectorCompositeType, } from "@chainsafe/ssz"; @@ -8,15 +9,88 @@ import { BEACON_SYNC_COMMITTEE_SIZE } from "./constants.js"; const MAX_BATCHSIZE = 10000; -export const LightClientUpdateSSZ = capella.ssz.LightClientUpdate; +export const LightClientUpdateSSZ = capella.ssz + .LightClientUpdate as unknown as ContainerType<{ + attestedHeader: ContainerType<{ + beacon: ContainerType<{ + slot: import("@chainsafe/ssz").UintNumberType; + proposerIndex: import("@chainsafe/ssz").UintNumberType; + parentRoot: import("@chainsafe/ssz").ByteVectorType; + stateRoot: import("@chainsafe/ssz").ByteVectorType; + bodyRoot: import("@chainsafe/ssz").ByteVectorType; + }>; + execution: ContainerType<{ + withdrawalsRoot: import("@chainsafe/ssz").ByteVectorType; + transactionsRoot: import("@chainsafe/ssz").ByteVectorType; + blockHash: import("@chainsafe/ssz").ByteVectorType; + parentHash: import("@chainsafe/ssz").ByteVectorType; + feeRecipient: import("@chainsafe/ssz").ByteVectorType; + stateRoot: import("@chainsafe/ssz").ByteVectorType; + receiptsRoot: import("@chainsafe/ssz").ByteVectorType; + logsBloom: import("@chainsafe/ssz").ByteVectorType; + prevRandao: import("@chainsafe/ssz").ByteVectorType; + blockNumber: import("@chainsafe/ssz").UintNumberType; + gasLimit: import("@chainsafe/ssz").UintNumberType; + gasUsed: import("@chainsafe/ssz").UintNumberType; + timestamp: import("@chainsafe/ssz").UintNumberType; + extraData: import("@chainsafe/ssz").ByteListType; + baseFeePerGas: import("@chainsafe/ssz").UintBigintType; + }>; + executionBranch: VectorCompositeType< + import("@chainsafe/ssz").ByteVectorType + >; + }>; + nextSyncCommittee: ContainerType<{ + pubkeys: VectorCompositeType; + aggregatePubkey: import("@chainsafe/ssz").ByteVectorType; + }>; + nextSyncCommitteeBranch: VectorCompositeType< + import("@chainsafe/ssz").ByteVectorType + >; + finalizedHeader: ContainerType<{ + beacon: ContainerType<{ + slot: import("@chainsafe/ssz").UintNumberType; + proposerIndex: import("@chainsafe/ssz").UintNumberType; + parentRoot: import("@chainsafe/ssz").ByteVectorType; + stateRoot: import("@chainsafe/ssz").ByteVectorType; + bodyRoot: import("@chainsafe/ssz").ByteVectorType; + }>; + execution: ContainerType<{ + withdrawalsRoot: import("@chainsafe/ssz").ByteVectorType; + transactionsRoot: import("@chainsafe/ssz").ByteVectorType; + blockHash: import("@chainsafe/ssz").ByteVectorType; + parentHash: import("@chainsafe/ssz").ByteVectorType; + feeRecipient: import("@chainsafe/ssz").ByteVectorType; + stateRoot: import("@chainsafe/ssz").ByteVectorType; + receiptsRoot: import("@chainsafe/ssz").ByteVectorType; + logsBloom: import("@chainsafe/ssz").ByteVectorType; + prevRandao: import("@chainsafe/ssz").ByteVectorType; + blockNumber: import("@chainsafe/ssz").UintNumberType; + gasLimit: import("@chainsafe/ssz").UintNumberType; + gasUsed: import("@chainsafe/ssz").UintNumberType; + timestamp: import("@chainsafe/ssz").UintNumberType; + extraData: import("@chainsafe/ssz").ByteListType; + baseFeePerGas: import("@chainsafe/ssz").UintBigintType; + }>; + executionBranch: VectorCompositeType< + import("@chainsafe/ssz").ByteVectorType + >; + }>; + finalityBranch: VectorCompositeType; + syncAggregate: ContainerType<{ + syncCommitteeBits: import("@chainsafe/ssz").BitVectorType; + syncCommitteeSignature: import("@chainsafe/ssz").ByteVectorType; + }>; + signatureSlot: import("@chainsafe/ssz").UintNumberType; +}>; export const LightClientUpdatesSSZ = new ListCompositeType( LightClientUpdateSSZ as any, - MAX_BATCHSIZE + MAX_BATCHSIZE, ); export const CommitteeSSZ = new VectorCompositeType( new ByteVectorType(48), - BEACON_SYNC_COMMITTEE_SIZE + BEACON_SYNC_COMMITTEE_SIZE, ); const HashSSZ = new ByteVectorType(32); diff --git a/src/client/utils.ts b/src/client/utils.ts index abbfce4..da9f172 100644 --- a/src/client/utils.ts +++ b/src/client/utils.ts @@ -1,16 +1,9 @@ -import Decimal from "decimal.js"; import { fromHexString, toHexString } from "@chainsafe/ssz"; import bls from "@chainsafe/bls/switchable"; import { createBeaconConfig } from "@lodestar/config"; import { mainnetConfig } from "./constants.js"; import { networksChainConfig } from "@lodestar/config/networks"; -//import _ from "lodash"; - -export function logFloor(x: number, base: number = 2) { - return Decimal.log(x, base).floor().toNumber(); -} - export function concatUint8Array(data: Uint8Array[]) { const l = data.reduce((l, d) => l + d.length, 0); let result = new Uint8Array(l); @@ -32,7 +25,7 @@ export function isCommitteeSame(a: Uint8Array[], b: Uint8Array[]): boolean { } export function generateRandomSyncCommittee(): Uint8Array[] { - let res = []; + let res: Uint8Array[] = []; // TODO: change 512 to constant for (let i = 0; i < 512; i++) { res.push(bls.SecretKey.fromKeygen().toPublicKey().toBytes()); @@ -79,26 +72,10 @@ export async function wait(ms: number) { }); } -/* -export function deepTypecast( - obj: any, - checker: (val: any) => boolean, - caster: (val: T) => any -): any { - return _.forEach(obj, (val: any, key: any, obj: any) => { - obj[key] = checker(val) - ? caster(val) - : _.isObject(val) - ? deepTypecast(val, checker, caster) - : val; - }); -} -*/ - export function getDefaultClientConfig() { const chainConfig = createBeaconConfig( networksChainConfig.mainnet, - fromHexString(mainnetConfig.genesis_validator_root) + fromHexString(mainnetConfig.genesis_validator_root), ); return { genesis: { diff --git a/src/index.ts b/src/index.ts index 509adbd..6306d79 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,8 @@ -import { ActiveQuery, addHandler, handleMessage } from "libkmodule"; +import { + ActiveQuery, + addHandler, + handleMessage, +} from "@lumeweb/libkernel/module"; import { createClient, RpcNetwork } from "@lumeweb/kernel-rpc-client"; import Client from "./client/client.js"; import { Prover } from "./client/index.js"; @@ -13,7 +17,7 @@ let moduleReady: Promise = new Promise((resolve) => { let client: Client; let rpc: RpcNetwork; -addHandler("presentSeed", handlePresentSeed); +addHandler("presentKey", handlePresentKey); addHandler("ready", handleReady); [ @@ -50,7 +54,7 @@ addHandler("ready", handleReady); }); }); -async function handlePresentSeed() { +async function handlePresentKey() { await setup(); moduleReadyResolve(); } @@ -60,7 +64,7 @@ async function handleRpcMethod(aq: ActiveQuery) { return client.provider.rpcMethod( aq.callerInput?.method, // @ts-ignore - aq.callerInput?.params as any[] + aq.callerInput?.params as any[], ); } diff --git a/tsconfig.build.json b/tsconfig.build.json deleted file mode 100644 index 90439ba..0000000 --- a/tsconfig.build.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "module": "esnext", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "declaration": true, - "outDir": "./dist-build", - "strict": true, - "esModuleInterop": true - }, - "include": ["src-build"], - "exclude": ["node_modules", "**/__tests__/*"] -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index c24bef2..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "declaration": true, - "moduleResolution": "node", - "outDir": "./build", - "strict": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true - }, - "include": ["src"], - "exclude": ["node_modules", "**/__tests__/*"] -}