*Create new version that uses kevlar/patronum light JS client

This commit is contained in:
Derrick Hammer 2023-03-28 07:03:15 -04:00
parent b6b419ec06
commit d9a3d30b7d
Signed by: pcfreak30
GPG Key ID: C997C339BE476FF2
20 changed files with 2249 additions and 159 deletions

View File

@ -5,7 +5,7 @@ import path from "path";
await esbuild.build({
entryPoints: ["src/index.ts"],
outfile: "dist/index.js",
format: "esm",
format: "iife",
bundle: true,
legalComments: "external",
define: {
@ -43,6 +43,8 @@ await esbuild.build({
},
},
],
external: ["fs"],
inject: ["./polyfill.js"],
});
export {};

View File

@ -9,22 +9,55 @@
"build": "npm run compile && node ./dist-build/build.mjs dev"
},
"dependencies": {
"@chainsafe/bls": "^7.1.1",
"@chainsafe/blst": "^0.2.8",
"@chainsafe/ssz": "^0.10.2",
"@ethereumjs/block": "^4.2.1",
"@ethereumjs/blockchain": "^6.2.1",
"@ethereumjs/common": "^3.1.1",
"@ethereumjs/trie": "^5.0.4",
"@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5",
"@ethereumjs/vm": "^6.4.1",
"@lodestar/api": "^1.7.0",
"@lodestar/config": "^1.7.0",
"@lodestar/light-client": "^1.7.0",
"@lodestar/types": "^1.7.0",
"@lumeweb/kernel-rpc-client": "git+https://git.lumeweb.com/LumeWeb/kernel-rpc-client.git",
"decimal.js": "^10.4.3",
"ethers": "^6.2.3",
"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",
"ts-essentials": "^9.3.1",
"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.192",
"@types/node": "^18.15.9",
"@types/read": "^0.0.29",
"buffer": "^6.0.3",
"cli-progress": "^3.12.0",
"crypto-browserify": "^3.12.0",
"esbuild": "^0.17.13",
"esbuild-plugin-wasm": "^1.0.0",
"prettier": "^2.8.7",
"process": "^0.11.10",
"read": "^2.0.0",
"typescript": "^5.0.2"
},
"browser": {
"crypto": "crypto-browserify",
"stream": "stream-browserify",
"path": "path-browserify"
}
}

5
polyfill.js Normal file
View File

@ -0,0 +1,5 @@
import * as process from "process";
import { Buffer } from "buffer";
self.process = process;
self.Buffer = Buffer;

316
src/client/client.ts Normal file
View File

@ -0,0 +1,316 @@
import {
Bytes32,
ClientConfig,
ExecutionInfo,
LightClientUpdate,
OptimisticUpdate,
VerifyWithReason,
} from "./types.js";
import { getDefaultClientConfig } from "./utils.js";
import { IProver } from "./interfaces.js";
import {
BEACON_SYNC_SUPER_MAJORITY,
DEFAULT_BATCH_SIZE,
POLLING_DELAY,
} from "./constants.js";
import {
computeSyncPeriodAtSlot,
getCurrentSlot,
} from "@lodestar/light-client/utils";
import {
assertValidLightClientUpdate,
assertValidSignedHeader,
} from "@lodestar/light-client/validation";
import { SyncCommitteeFast } from "@lodestar/light-client";
import bls from "@chainsafe/bls/switchable";
import { PublicKey } from "@chainsafe/bls/types.js";
import { fromHexString, toHexString } from "@chainsafe/ssz";
import { AsyncOrSync } from "ts-essentials";
import * as altair from "@lodestar/types/altair";
import * as phase0 from "@lodestar/types/phase0";
import * as bellatrix from "@lodestar/types/bellatrix";
import { init } from "@chainsafe/bls/switchable";
import { VerifyingProvider } from "./rpc/provider.js";
export default class Client {
latestCommittee?: Uint8Array[];
latestPeriod: number = -1;
latestBlockHash?: string;
private config: ClientConfig = getDefaultClientConfig();
private genesisCommittee: Uint8Array[] = this.config.genesis.committee.map(
(pk) => fromHexString(pk)
);
private genesisPeriod = computeSyncPeriodAtSlot(this.config.genesis.slot);
private genesisTime = this.config.genesis.time;
private prover: IProver;
private rpcCallback: Function;
constructor(prover: IProver, rpcCallback: Function) {
this.prover = prover;
this.rpcCallback = rpcCallback;
}
private _provider?: VerifyingProvider;
get provider(): VerifyingProvider {
return this._provider as VerifyingProvider;
}
public get isSynced() {
return this.latestPeriod === this.getCurrentPeriod();
}
public async sync(): Promise<VerifyingProvider> {
await init("herumi");
await this._sync();
if (!this._provider) {
const { blockhash, blockNumber } = await this.getNextValidExecutionInfo();
const provider = new VerifyingProvider(
this.rpcCallback,
blockNumber,
blockhash
);
this.subscribe((ei) => {
console.log(
`Recieved a new blockheader: ${ei.blockNumber} ${ei.blockhash}`
);
provider.update(ei.blockhash, ei.blockNumber);
});
this._provider = provider;
}
return this._provider;
}
async syncProver(
startPeriod: number,
currentPeriod: number,
startCommittee: Uint8Array[]
): Promise<{ syncCommittee: Uint8Array[]; period: number }> {
for (let period = startPeriod; period < currentPeriod; period += 1) {
try {
const update = await this.prover.getSyncUpdate(
period,
currentPeriod,
DEFAULT_BATCH_SIZE
);
const validOrCommittee = await this.syncUpdateVerifyGetCommittee(
startCommittee,
period,
update
);
if (!(validOrCommittee as boolean)) {
console.log(`Found invalid update at period(${period})`);
return {
syncCommittee: startCommittee,
period,
};
}
startCommittee = validOrCommittee as Uint8Array[];
} catch (e) {
console.error(`failed to fetch sync update for period(${period})`);
return {
syncCommittee: startCommittee,
period,
};
}
}
return {
syncCommittee: startCommittee,
period: currentPeriod,
};
}
// returns the prover info containing the current sync
public getCurrentPeriod(): number {
return computeSyncPeriodAtSlot(
getCurrentSlot(this.config.chainConfig, this.genesisTime)
);
}
public async subscribe(callback: (ei: ExecutionInfo) => AsyncOrSync<void>) {
setInterval(async () => {
try {
console.time("subscribe sync");
await this._sync();
const ei = await this.getLatestExecution();
console.timeEnd("subscribe sync");
if (ei && ei.blockhash !== this.latestBlockHash) {
this.latestBlockHash = ei.blockhash;
return await callback(ei);
}
} catch (e) {
console.error(e);
}
}, POLLING_DELAY);
}
optimisticUpdateFromJSON(update: any): OptimisticUpdate {
return altair.ssz.LightClientOptimisticUpdate.fromJson(update);
}
async optimisticUpdateVerify(
committee: Uint8Array[],
update: OptimisticUpdate
): Promise<VerifyWithReason> {
const { attestedHeader: header, syncAggregate } = update;
const headerBlockRoot = phase0.ssz.BeaconBlockHeader.hashTreeRoot(
header.beacon
);
const committeeFast = this.deserializeSyncCommittee(committee);
try {
await assertValidSignedHeader(
this.config.chainConfig,
committeeFast,
syncAggregate,
headerBlockRoot,
header.beacon.slot
);
} catch (e) {
return { correct: false, reason: "invalid signatures" };
}
const participation =
syncAggregate.syncCommitteeBits.getTrueBitIndexes().length;
if (participation < BEACON_SYNC_SUPER_MAJORITY) {
return { correct: false, reason: "insufficient signatures" };
}
return { correct: true };
}
public async getNextValidExecutionInfo(
retry: number = 10
): Promise<ExecutionInfo> {
if (retry === 0)
throw new Error(
"no valid execution payload found in the given retry limit"
);
const ei = await this.getLatestExecution();
if (ei) return ei;
// delay for the next slot
await new Promise((resolve) => setTimeout(resolve, POLLING_DELAY));
return this.getNextValidExecutionInfo(retry - 1);
}
protected async _sync() {
const currentPeriod = this.getCurrentPeriod();
if (currentPeriod > this.latestPeriod) {
this.latestCommittee = await this.syncFromGenesis();
this.latestPeriod = currentPeriod;
}
}
// committee and prover index of the first honest prover
protected async syncFromGenesis(): Promise<Uint8Array[]> {
// get the tree size by currentPeriod - genesisPeriod
const currentPeriod = this.getCurrentPeriod();
let startPeriod = this.genesisPeriod;
let startCommittee = this.genesisCommittee;
console.log(
`Sync started from period(${startPeriod}) to period(${currentPeriod})`
);
const { syncCommittee, period } = await this.syncProver(
startPeriod,
currentPeriod,
startCommittee
);
if (period === currentPeriod) {
return syncCommittee;
}
throw new Error("no honest prover found");
}
protected async syncUpdateVerifyGetCommittee(
prevCommittee: Uint8Array[],
period: number,
update: LightClientUpdate
): Promise<false | Uint8Array[]> {
const updatePeriod = computeSyncPeriodAtSlot(
update.attestedHeader.beacon.slot
);
if (period !== updatePeriod) {
console.error(
`Expected update with period ${period}, but recieved ${updatePeriod}`
);
return false;
}
const prevCommitteeFast = this.deserializeSyncCommittee(prevCommittee);
try {
// check if the update has valid signatures
await assertValidLightClientUpdate(
this.config.chainConfig,
prevCommitteeFast,
update
);
return update.nextSyncCommittee.pubkeys;
} catch (e) {
console.error(e);
return false;
}
}
protected async getLatestExecution(): Promise<ExecutionInfo | null> {
const updateJSON = await this.prover.callback(
"/eth/v1/beacon/light_client/optimistic_update"
);
const update = this.optimisticUpdateFromJSON(updateJSON);
const verify = await this.optimisticUpdateVerify(
this.latestCommittee as Uint8Array[],
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}`
);
return this.getExecutionFromBlockRoot(
updateJSON.attested_header.beacon.slot,
updateJSON.attested_header.beacon.body_root
);
}
protected async getExecutionFromBlockRoot(
slot: bigint,
expectedBlockRoot: Bytes32
): Promise<ExecutionInfo> {
const res = await this.prover.callback(`/eth/v2/beacon/blocks/${slot}`);
const blockJSON = res.message.body;
const block = bellatrix.ssz.BeaconBlockBody.fromJson(blockJSON);
const blockRoot = toHexString(
bellatrix.ssz.BeaconBlockBody.hashTreeRoot(block)
);
if (blockRoot !== expectedBlockRoot) {
throw Error(
`block provided by the beacon chain api doesn't match the expected block root`
);
}
return {
blockhash: blockJSON.execution_payload.block_hash,
blockNumber: blockJSON.execution_payload.block_number,
};
}
private deserializeSyncCommittee(
syncCommittee: Uint8Array[]
): SyncCommitteeFast {
const pubkeys = this.deserializePubkeys(syncCommittee);
return {
pubkeys,
aggregatePubkey: bls.PublicKey.aggregate(pubkeys),
};
}
private deserializePubkeys(pubkeys: Uint8Array[]): PublicKey[] {
return pubkeys.map((pk) => bls.PublicKey.fromBytes(pk));
}
}

532
src/client/constants.ts Normal file
View File

@ -0,0 +1,532 @@
export const BEACON_SYNC_COMMITTEE_SIZE = 512;
export const mainnetConfig = {
genesis_time: "1606824023",
genesis_validator_root:
"0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95",
slot: "6046691",
committee_pk: [
"0xac2154953fb292a27604162a2952f95d20b2438acb1d460b0d4ccb5899788b8d33437e38f3434ad87bcb5141f853be70",
"0xac61b1d8baac8251fc13bb71992f7ded3b92a596c37ff616eac136f73f3e0d46bc18dd5a7e3bef23f2b094f78adfe6af",
"0x89bd0a7ff808295f7ad77356a20d58252c4cafc38c1bfdf57a6675bacd192a549c80b6c0f0e67fdf647e8f31e5b9cf8d",
"0xb2a024a13871b071dec5b6c6cfbdaa5791646cb1f60f2b077dba7d732fcc6d6b806ed7e39af1218a0a6f94892702c953",
"0x9983a8af711066bbcc9986db271cc7b41d64a1dfe9fed0593c3d904c9503333b7c23058637ecb94fb5b4769626a4757b",
"0x8700b39fc4738816227440f20fd8b22a52d9fa1da598fc1509969073beae34e23fb0f963e1007907b47801ac9b3aa30e",
"0x891e56d074bf4500ab2ba55ff77a08eae23009473bea1f8db5a8479246ede43132f2957cff657c97f00b3310ddf6d202",
"0xa398b7effac7defb1cf0a17e968de79b8c78deebd8a5b549055ff077c028ff3b7063310e0eb9183f26fb2ce89963aaac",
"0x860c7c608d467a81503a6337d058ff2981aa8cc2426f644b896faa57a6ac56627a02be2c3ee4cdddbca85a39f395d439",
"0xb3f6f13b1da0e8cb469f5709fb724001319ee0cb80c9f2b39438a696357dc782a63f1189a86e2cb7520badb421f256a2",
"0x8870e0b2e695ede2785e6278edd355da579210e84154b5425ccb7eafccee66781f0fe34ce7bac217969b7af51609579c",
"0x988255fc7b6802706be2ab708bb9bc8e78ba655640862d84ed37eee3f28489a221079c37bb7656039edfa2982cd719fc",
"0x9014f806cc97abfba9a33d0740b236fb8d64d7433a525cef08e1725758e7a0c02beb072d2053873580b4a6104e1df68b",
"0xb186fa4fdbabfff3cdb1feb35633e36db451a535427c504fc56a61d3759b3511e665a2020d4fa7387bb7ddaf7a45c173",
"0x8511d7d04df823ba159b586f4e0cec92304af11c10ced9a4453e87e65c1e31292e2b972084477b52debbda2be15f5912",
"0x972ad915f0f83c933fded9937ab79ef34e1a2932aab1e8dca73c922100033b68f6ee53260feba9aa65bdfcbaa2f2e96d",
"0x87081ab9ed37e7f9c901beaeb90eba808134c74d1268011cd589785a8862523913438ecd620fb41958f1dcbf5bc48438",
"0xab0f051952cbcbae0a21f38f17ae6e3243a230ce894dcbbb60a8ef1147e9b7590963a68e4e9261fab684b2dbe735cc29",
"0x96f42c3c4eb39a8a27126928a781a1bad82fc9b8440d04d4fbfa1df30be22a478fb6f607bfd00d357eef8982996fbd16",
"0x90e26a69f3c9328889da4dee470c025be883e102ea1e48fc1d86697b70361820960b5d01563b1a5ab78001c117408f2b",
"0x9153f1e511e569c534d663a45153bb22cd1741ad0033425450a2958a314e2613ccbbe2c395e408ea77de629d86c3526a",
"0xa083abb8336f02926a9ac2b4901bc227d4affb7778fedd6c3c81e445e7ac40a7fc475d9126e31541849d7fbcd0afb3d4",
"0x906fd4d31cb160d1c53d0b0b61a51efa5dc7e50cfc3ae982ab16d3f6153b4a5c114e7acd0c6e72c1572bb1b80ab74358",
"0xac20f0e8975789e4b17754c189ee1464d6bb7a8078225c60137964c26f36a2e4c4cac643a01467731f6f48e2ed44a69e",
"0xb517743ccea6f77054da3855193eb02e4bb823be3c624bfcfd306e3080c1a1b469b802381442376770a729d920ce2700",
"0x82ef4787c3ea79599517c403f7390fb78bd1d75bae536237d7cb5c55a0b9245d8ffe1a1c63612c5a1bd2ce216b29abca",
"0x93747d6bee6108f5a57dfa965510a99a331f6a366530cf1b2386f00978f562744fe3ad1960d7f48b0a0c9e5f2d65766a",
"0x851341d5eccd7901dd1e28096cba68040fa5aa15fa17de157b74f5a9af738b8c822beb8767e936c97273bd365e421c46",
"0xaccbf842d8ac35db1129847e9d50692df0d83674b1ac38ae35f56e53c66f72845e728c95fb8f73a400b55c6204a7f322",
"0x91f096011b8f9cb23070481a90919956a10b8f507769c57861e25470021954601747ebb8a88aa47bdfe13f2ae500dd12",
"0xb31dc69d3f3a9afda62111094b6753065dea87f3c47b03276312524223e910b707c7e079b0b199bef2b8dc84063c4bea",
"0x824f23ceb4afcc36d59b0d6d50d7e234ffeec2373272e8dac353a7200a15b739a3a957b0a8fa5adb233be467e2357480",
"0x89c183fef67ad8433568f261ae98974da9ebc5c51b337f18a65ba739fcf019d0290da0a5df982f1de598d3ddb364bb4e",
"0x95fd442553d2d190848f2f206862bedcceada9b0d68e4de017f8fa61f4dc11ffbe3615d569865f2d57b4b3bcaef2f0a0",
"0x836761fd5f9a83ba5c531b0c50703b47b92b5588a03ac5a6de9283023b48950fade6685f4120d29db2efe655f9d4e5f2",
"0xadb1a2f31adf106d4a2539b3991f32a5b4f4c7d215ef46784615ebfcc1dcbf077a8d9431d055d9736d0326138570e978",
"0xab19a893f46fe84a1b003ddd992b4c0b50beb2b979e9237203e984f237b6499081fc8cc4966c78115aeaf054684d308c",
"0xa1ef5ad999d1b3665de4cd5d516c8bf8cb831ad52344af359268d317d4abcdb3f32e804189a8c091871dd5a418cfce61",
"0x98f1f219f616b36a75e6418608b4729fb8859a098b9785191ea19a887137e9fb0658a4e0134db0d15e0c6f955a0b5829",
"0xaef97324a64f1f67ef706f261b44c1b90ad8a1a533b2ca15ea6b9faa5d681cb30c1622da558adb6ffb90515c87558c06",
"0xab8b378bf27f7ef6eb5e22979c1dc3905e1ecfcdfbbe58e73ce7f1f5734cf1006a0224ab499941b12c24584a42d67139",
"0x8aa197f39520416779ae2c5487deae4e91df104fecc5ac496ae2993dcd908e43a8003e88d4c7eb232f81a4fc3e453dcb",
"0xab4a54b7d3b839f4c97fe2dae8ef149983a16cb15feb13b4e2dfaa97374ff99aaade102a585b772d717f165d8f421365",
"0xa34e5130334a98865a2b8a5e0f0ebb847247974fbc7105374ecc819de25f070c1cf7650b0f7825eec2be7d0d31486a3f",
"0xa5e926709eb027938329dd00a22a122c9e68104c6fb6751724a26625ea55482bb7581964bfa2fcbe9d0af8096a88312c",
"0xadb295586f2fac8a503e6c7c53183f2eb96e05cb6c38b34c5b939a1a413282503464d782f6b6ce5350459f561ff27588",
"0x83066cd4554e51138948e354b7e3e018eddf02b71fd0106effc3a099296422b7316198b70194cff15708cf862f873c06",
"0xa3a661f075c844111c98c62c24dc25ebafded96689ad4f40e4d0bb1365aac01098c90f7f76f6f7e01663dadcd29433d5",
"0x80a28cac5cc54fbbf0c0c37c0ba968a3d5f2ea7ea46b7ffb3102cdd1371efb5b65d93380c6b5edfb84e274913508bdae",
"0xb0edc7b534b6cbab420d76392a83d3a8f26eab4a8d3696b29476ee7e6f89c5dd6f3eae16514785ae8366e8535822f0f0",
"0x8aaaab7f3b8fe248a57109978f5f8016b5be4d525d9ecb2c7e712141cf958edbb6bde18e79c6b928f469569584b0d108",
"0x8f44d63f1f09856673d34726eaa49d6f49165d0e0a1a67f15fefc2b3dc51bae41c365d03309c05f5893d286f4b81b14f",
"0x8d2fb449742a5d0ec18c91e72ec6de33ee1117af59593d80a5d2b940df8e2d05e7437a3582ad677262930f7524bd99a4",
"0xaa821c73859d570b79629d697598ab48a9cc816bcf9e0487ec6c6febfedc50325293877400c49255b4ceec07e2ddcace",
"0xa7e622511de149e700f230c730b68dbebf5afe6a627693619da2fb3b059db155806a206862c1432cc39638d00b368cb6",
"0xa7282c90faef292531548d1a221b5a07d99aeb37184f5a7715205c75479bbc908fcc67bc07b4ebdfec7b6fb2229aabd7",
"0x97c32344bdad6fea1550faccd633d767dd7c3116e09c453ef6ac7ba6740f80b4ebebb4dbdfae9125cd90076535dc5d16",
"0xa3fb5ea7195d98dd1b8990abc5ec16e8978f9d1705883fa0a665d05aee7983228ed5995a3f5bc24416dcf9204f1c12d2",
"0xa5cf59aef5755678b4c54bb486110c7708ab13f7e5f642f605830ea2a285b884d871de4c398c0ecdabfd73081fe8a443",
"0x85c11bae0968c4296c00ba0216d5abf39d670d64e84820edf761dc066295867082f8aec6fdc94a4617e3c3e211c2c115",
"0x8d9dd55203011e3d80ad08bffbc285f95a30dd1ebc4e11870e581d6b24d4682338c683b407a93e24cf3e50b71b6aa4a3",
"0xa1574a499db213c033928d728eda7812acdbe83971e4380d1d3ac50ca9747d1024a0b95837f940cf84f0271fcefcb579",
"0x833247d97973aa04ee8143af434215e9ed43c809dc232915cd13a05354bec29186bcffa427f4a2cb75278d1302194145",
"0x992761cb86061d188e4fd0340dbe074e389121afd821eca03ecadd378dbc1ef92839ec8407e9c5fdb6a4180e1e354651",
"0x8e035b52f2759a7c90f936cf7e4eeb9481ce3ddc036eabb5ecaaa8e2f40fe28f949f9a6730fac7189c64aa19066c7242",
"0xb970ccc4584826668fb1289ef45bb92eab0f4fd856281c28f771d1545f4494934a3ede1e215c441e8ab14c030b9f8470",
"0xb4a474fc617fcc54e08abc40ed41ea09241fe6894b79f3e9356be85889d98fd9d4063cced43103b8c3fd0013e1909503",
"0xa0e21ccf14e8f609ab7be489141c5519b157f135345b2ffbfca61f83ee69ce71da543261833c523c017896e193b8d03b",
"0xa53599ac336908004b0380938f8fdf90bce83666b948708273b399e0c4e07824a033ec6725f329a5cdd572e04a06801a",
"0x88ecd5e25ddb207748cc24f4f3a5a32cf7aa0054a94af9fb5742dcd46883fc71809532998cb289344e2818bdbd60e05d",
"0xa641713311a1f14e2e20eb7f992bb3dd14aeb404ff0a8648ec6451a04cc428ffb9de3ed984a49f00949ac3ba3c2d75b2",
"0xb91a617a603bdeb2df2d9c363093bfe8c398af726546c38deaa1652c5abf13b596be7dadeab2aaf7263af907602ce7f6",
"0x917c45d61c9f887d505183519b7af37541715d2516e358ff22f2205e7466073d33589ce32b562e1e892a249e14f89982",
"0x83b738c49524eb38c7d7b57df1a5a9da6791f353070a6fe9f472702a6637b419db69556e57903b7ef32f42d35c55658a",
"0xa20623e777764d3859c0003991de2a56d067d4a21757ce9535e0809b73d8f1dda76dbb9ce8e80097794f3d0ef17aba56",
"0x8a3333a9316c348daef2acb393834b3094cb9b1dfbba6c93c51f04608ded0b3b00474cda55c7bf4a9c1846fc5261eab0",
"0x8fa103b69145c967a4a239f5b47b5fdc44c7904309e44e4da13cac5563f942417519870414731855a9a77a9d17f72641",
"0xa47ea87110df9e182e7416197510d6ca41ad4d21f7ab4bc5b1e24236df3d273c0a3d4abff17b522dfc7c7decea37bb51",
"0x8869b20929e093a8509ad6d7c464b56a319648daae13de12587e99bc63a9faff8f1510af24831b3f60bb7d106802655e",
"0xb22f852eb5ed2e76c8ab238cd680bf0f5f6a1748a1fed5582cc854debea3001fe1d55e2a82d8a8dcac55d67736496feb",
"0xb96b07b3a4a8173d172f67686e806a4f0c91bd85315e7dd593f9c61e6e86149a90eec25048b3725d594e88e271b579fc",
"0x9815a947bdce59b25f839a033bc6144b4daf3715e9301e3087a4cb781d0b2ffb69ac6ee53d62f39e25edcecea0c3ca00",
"0xad6c6b59f52efd7e5a07db59689a7d324cc9176220a93a4a25b8acc0f9865fa8af59a464f32354d71d2c46d0f07f772a",
"0x9732d7d58462fb7097d96698a71f13cc6fe44eec925ded9662b9cd27328895155fa9c6ef54daaa60a6c9d22c33752fdc",
"0x8242d1e2ac762cd37065969d388e636bec4cb9615ff9d5ae166800fbfc8969b306e0e50361a75b25f34db97a5fb1ff92",
"0xb0d9b515a9c689fc5b74e61577d3bbe6528a1ef49a9850e4f0af67a356ea6cbf144848154d89468543b200849edc04cb",
"0xb1998d029255ec9ca9c409c11259609a6b4b93d20b499fafa480d6287e2d11080b79b21d898f2b021468f98e87e737fe",
"0x948cbd5c8571d051b6fb411d3a3cabb2eae0971f3eb7407f242409fd6c4852a8ba76c1f4016cb26d20e7e06423062f3e",
"0xad9f16b12ab53039f424e12d65e47a1d63b1173b10cfd34607d0e7a010c46d9bccf451a6cefeb9b42de0ccbb88b15ccf",
"0x91f8575a367423a90ec6170d1ee55196beaa3cd2a2cfa98a6ba90c1758cb4fa9b03414b58e19a5d3e82d66f8187ad283",
"0x87fd9c53a7b1e2f748a3d5a498ff3f5760a7c5733b41de295baef349b673c246661535f04b36a64b7556eaaa8a7a2797",
"0x83ea795760338e322b6ae9081d0a6e3484bc7be696641aa5dcc502ecdbf1d586fdd69f2278847f791056df2f99477a1f",
"0xa37d999b8ed427f793143b74acd528f623a7f1954848e1b7d2c7964166a6910de5922ede1d1ec838abe9bdb45f871834",
"0x99887d6143a50745a00bceeaeb8b13d7777b902b4e4aa8c8499926767b2f340a732fae66f19f423f981bfaef020729b9",
"0x8637794599b358a153589e8da1b151d856d02db7f10be662595566abf3896bd32f6c8236b8a514ea2f8de8d41d305ada",
"0x97f61eedbf869f6b91e185114f4a838b55900b9797e5ce3764c68398261a319da5093749afa144b0128e28b111cddcf0",
"0xa8b761700bbb4aacc9aece0802ef779e1bef851fc43d0b5ed1c683aa299cc3ebabf65855bbbe10ae7466f4ecbc5b96ac",
"0x84623e341c2e3604e301673123ab1c342ee7a434a24ff60bc24a1004e4e698cba5835e67da176b1d547d64e7695c8e2a",
"0xa3ceab358e670a5c188d3f9420966bdf223434360cb3f10763670b166fd857522961cfe6cb832c5908aee3b4e10b38bd",
"0x97eafebaa31ece76781bb8b3ee3d5bfc6f1be8094b3e976c3cb87daa9f79a5a45b27cc4d3ad0a94627a775ed46cb5e56",
"0xb3733f5030f84c373e2481a2ecfbf1c6ad691a05ea48b64064e3abb51d38c369a1a290e17254c5fddd21d6062c074905",
"0xb1611842d97187057010278b81b9a0ffdc7aaf8ef1ddf2fd8d69eedf1f8dc3702843b5adbb1aa0fa05ee39dfbd582ab8",
"0xa94f7d93b520e5d063df8b3fd5dc11ae847474f6c62a37824856ee230697376ac21a7ab9e2c8f7e2d6ed87ed1bafeb73",
"0x9671f924d0bce9cd26d27075e52b460a9451922ac800142e4dcdbdf1e041b906bd3f87bec6de2fe12d7511c0064c9b2c",
"0x8e1b6985c7b0bfbaef75cab9f4a9b001747b3a028510d1e1128c306e6e5939367e1a0abde6e5a79ab0d336bced0e8426",
"0xa116d6223f13df96d086c07ae371cf5417d1ecf6ecc749cf2d73be54c7877e58768d7df26dd664dbb267ec53460bdc39",
"0x868a7d95670b8e24d31bfeae9d958070a5bd389caeea3a41e54a3b4021d0801658baa83e71219d6670a13cdf0105ba6c",
"0xb7c43d53ab11a34d2a1e487c3a2c44def71e8c63d7c7447e940907bd26dc6c4850f6bbfc01c5175e84c9702be99942b2",
"0xab3d91789f9c2e0bbed3fb58033bbd3bc28c077a0cacb9c7a5ef0e308c2d0da0edf2b6deac21df98b25381a617f725da",
"0xb1cc0cba5b487e49402fcdc02f20bce9c44c2afae1816ad60361025486b629ae68c02355d91a2804bbebdbe091312a4c",
"0x81abb5f3d5aa066bae2742d3bcdbc8a8b96ae2d2e62f78827d51bf52fa57b9fd4bdecc9ded211407ffedff45998cc931",
"0x90e8bd7bb48a0b486a8797995cdf3cc0b33941dfe83ca61bf286c14b5cfe744321899b4863032063c9f5792c9fb40f79",
"0x98f4ca7bc46de1e9cd9de0e180c779d332c13740470253d84fc3c055a6b291d036916d8d5ec477e383134dc0371da8c2",
"0xb08da88d8030d724768ae59b17a7d8a22bc1b3a1426c9fa1f4b63fd84699c58e813b3ac83ad842a74b0c6f1179e703ee",
"0x835ea95d07d13a5a8e1e419c46ac34ecd30bf0a849da204cf9ef168d8312243706c12cfce3276f408e70ba55e148f61a",
"0xb91664052346378b7a29d4ea9e02be301f7d659ef819a94c9d0dc7b14776e416c9f0e6ba721cc89c714f6659f9839a9e",
"0xb4341164dd17e68a2dc4b845d3f0e532096eaaebd7820fe3c87fc75e1e5741569d1386bdbad2909f3d6038bf194f780f",
"0x8014ecc0c97f5a477814b964547e87d77e4288a229efbadfb06bfcaf7650964e21bfa0cb2f3cc356d53b66c9e5c047c7",
"0xb9247aced49425bec5bf1c5d94b346266cbcbd11c74287567a39530749cd537bf1c5cde011f61432eb79b8c40dcabab3",
"0x8a3a77d5b570c7fb5bb804f9de87febc3545287f22588d1d48a0f4e9b43dd83717a79693faf913b78f435d0af531cba2",
"0x91acad3ae695588b5b388df0afe9a4a33859c8846adb4b53ce6bb968bf2f486c4b3e93d5c52dacbedc13e89b58d546ee",
"0xa55d11d5bf9774088c7e979f8c8e211fd0c971217ac5c314a3bd8934e40bc518b001f317564a09992a1e163fcdfe0a84",
"0xb543d2855a4bdb4c25f10370844966b21d523a8cf6c838983e7b4966c1ca3d9785b3c893f9b5d088e1d056545e7798ac",
"0x8f762a160728304223e74a89e9a20dbd2cbf736f8fe44e570a929814d3d39dfde17991cfabce2a6b498cc07cecffde60",
"0x822c22863f22e3e5045283e8b6895e0769ca44e057fe75634b3e73d3dd97966a7efc464d35b6f4ed21a49585cf2fb383",
"0xb49894c21b0e9ca2acbc8a7023b36c3c582557a785c9d64cd5c70a06d286e4b707a16b18ffbc43dd1439e2d45b7cc446",
"0xb0b64b13a4b4aef474da021b6fc5358a85f3c5d3e2c94170c8163dc9e6513046c21f6b0f3f1bfa993a7e9646727d91f9",
"0xa0db5843ef5d20a387be71821a914150a520b98d2e82b48678c476493469db48ecf9bac4f75718f2dde05d9d85f664ac",
"0xb1570013916bc729000d46927590f17e0533c04331ef372ef0770ca18db143377706b1a034831918720fb8912d2e4a68",
"0x9563b2e364939721b26c504441714e09194581c3ab63ede45e9b6ee959ebfe6a540946170b4720d3afa3d507837864c2",
"0xb98ee9dc5718cbbd2575e99c79579b226f270e66966088f31a0a500f15b117dcbfa1bc834b380c45eb92f7f48dd414c4",
"0xa7041b50a55303f55d8e33f78e86b36f8e447043791caa409abf6aee00a73bb46abdc63b118592d2527dc95e0dd910f4",
"0xaba4a6a4f1730cdb5732f81463357cef916e76181d2c8bea181f1d50f67d78ac22302503acee53704d4a4477723cc8f1",
"0xabc0fa4979a4b5c3f98ff7ee60fe2958b66a5208751d32a24a340010e3f668a17639852be6866f7b780d03634a8e690b",
"0xac73b575b07a562995875b93f4a5d286f4695fa8b3197eddc240af98af65cf14cce6515fab4ec149c9c3062b31f59a71",
"0xa12141f2a0e7e28ec563bd2183979b323b2db2ace46634caa7654fa9e78a7d227d5499108d9b824bbf41bb0a7eb36206",
"0xa837d99607e21452882d4b6e8137430773b58f6e737ea95429fcba5d212d6aa521efc4d852a2bd87319d3132414f9ad9",
"0x8f9cc360fccba60728885da856b77ea759c72f74e6159feb9efacdb6d9d0a3f405b45bbd3e4b2a1f19e7a7050e4db188",
"0xa9f934fc0fb5f079cea74c9cc635b9c9f7e90102de2862f1a2e513cd4ff270027e979db00393c38ba938159486ca6466",
"0x883efd70660f9cdeccf8d43172cebb6793344587a9615f15b13acf63218e517193c9e1d8a4a9e7543ac4ec340ecb3f57",
"0xb255febbc7c93fadfa3a54090d1af2faa64c81d65f21a5901a6e44bddf9f6bdc04e6ac9e415bb852f8f8e1a66c9b9e1f",
"0xa83d55b1120c0d67f02b04bdcbdfd31bc391b1a10332d3745a2b497718027dec6fec99019969f11bd2f2c702953531d1",
"0xa1ecfb8d9ae9ffbf62e288e359125f5c9b36486b208aae354ee4d4c07d5b390cd104c51ccd845a5cc59ed71d277114bf",
"0x92f80073ffe7a8fce1d65ff69fd16b28a5951c53bc1a7fd79da23e7092c58de49f1e2a2e69c8d6b38db390551bb4c79f",
"0x91d9c03413bd2c546b4c2f00f2f1c686a8d7ab9340bd844d25adf6452d464d38cc5f520149c05b2ce2b6cb530838dd83",
"0x83a268bcdd59a4e1c5248fa9557dde8ebfb2b629f9a52cbe5b7a32ccb58cb3ceea068e513b1e531df67cd611a703cbc6",
"0x939c12bf71339290bf4b2fb5804d49ef93abe39082c9ee192d722ba6760d5943e06190f0bd95debd5c2130beb6f48477",
"0xa915ebb7ca912935af76ea0254ce30e84ead705a94e822e4dc52cf2db90ff1dda8fbe6c8a1da75e185c839f14ab17343",
"0xae1e55695fcfd95d64048767d52bd4149d69fa0a8775368e55d03e46fd2d90768f6c6bc82b80917dc58159ce6ecaa450",
"0x8d86df85b0af7a02634bbe2a586486569ee2d68c5311c8f580590523e249b82f4ec623b58daa79e745e1b841ddb5c67a",
"0x8496e1fdd84b6092f713da75c1e9cfd1b9ddee43c8d068dd29b4d26ea850a29876ef5948f26d55096507f1b7ef34c34f",
"0xaf0775c01072b6e26024e5b161ec9df52c5b5c8962610e34627a43c064d08813f3387fc9e6cae9cffa7599d3670dfa77",
"0x98f7659504c4e23589634ba13a065dfc41cf125b1f005dad2fc871238b5d8644d0fbb146461ad3d5a3c847ed67f0148c",
"0xaa6f5faefd96263bc8d489f5f7ce90b83e5b845ebafaaa068cd3ad80bab63cef688d54e47f244af733a6259492238170",
"0x9545334a457fa6de1bcffe1bfe55022ae17c483f9642f4df00f3f8037ce48c41c85c3856fe588ce5f1d066b449a322e6",
"0x82de827fa10a7f01d3745f1541bc2cd87913b7035c25bd000a7c10a4361a3ad85c8481aba46395b333797357ee15eddc",
"0x8619b0b264edfe61ba2f59b227d83789d17c628a0917f83cda02ec944f9501904b90542de28652d46d61e3533752aecb",
"0x8fe39cac7d857db0adb6d65cc6c2f3f79714fb8c6e07c4260ba75631850347af3fd49c70bf608d7429b7ee5ed17a7da8",
"0xae7872e385a04bfc3184d20e1b3b216a54da60b2d7d1d2df86e7d0e70ca1ce6c3ba6d33e0f8edebb828b463208db2835",
"0x8c9fad22431036ab8063cdd5ab4f0cc0064fbdb8a7d29e0d6c17877b013629a84114573f3a3e8912f9ca07a99f7e1c81",
"0x8601ccc5f8bea862cb7796d055c7094e3066ea42f01b8298cbac64c3b2fc17d8a8bec8a7c36632839352ebd8634e8c82",
"0x876568c4084a5d7a2c9fe51ca57781768d6ffdb325b8a0f632dc496e5e604cf982c8fadf53cd69db90217a3c1d733211",
"0x93a7b92211c21df28effeb352670d7bebb89064d4c7f489d6c9547c88864d41197a629f4995f6bafd6b90cd30632bf56",
"0x8dfd5323ecdb747995d960e2276de05d7b67e07fbc7f5a517928eb9e66a6ceb60076e6dd52ca3c11249dbcd50720338a",
"0x8f298743b93de69a023023991a1349d24b5ac437ec1ddf0abee9735ded134af1e50e0d1b98dbb4aa801c1e2b93ae3ac6",
"0x980ae9e57e236c382611dff680cc860694466db10598fc899aa75d380a9874b39e742dcb28df1155157d8ab8d764931d",
"0xa081115929d1676c832d49e4d51d0a6d9b4e097ac6c2f5f26eb40b5c197beda528f1bfadcaf5d21d6add367894d5ce86",
"0x8916e596ae84e5e216f51d17df8c668b243584358a9d1c2499cd538f2249ed3a0ca82b1453db1556f8696f6033d35f25",
"0x8a666e822d514f50320d95b540b9d5877e416cbd19f2296b561f2ef8528cda431afa49b069859afd9df064c75f781e09",
"0x96e286b5f5de90ad128750ebe305c4d78e8385eae0294f69f4b28c8e5fce4231a88fbff18cfb36861d064707b4cb3905",
"0xb57d9c68dd7c3c3681e40d8a0e5f7cd577e9488973d2174bea24a45c0b3ebe5436e738f36984b94593d61ad770baa791",
"0x96595459c3114841657593e67ee1822843b758f78d58ce8047ad4020fee5827ef5471ab5f1c5f7511137787a6410daba",
"0xa4edabfff449887ba41646db82cd7d267c3a2805c1abf3edadba6dfc8e36a0db8db93c4a9bb3e8a8c324e89090b7ab5e",
"0xaf54272ac4a0747fc35628eb3b9109ce8ba8409789784357a3513615bda340e5f76bcc34a4079d31cb2eda99f358c011",
"0x99c358228a324e6fec8fbfbe7e35964a65868d6194ea33f8022340e3e577dc37c8783e2108aa866b9a45ef46832896dc",
"0x9527829bf297fba0e4877058dd97ec3f967da844397768abcf94e90a804efa4640449e90adc05eb5c85404a7f2268e31",
"0xb6c5b5476235f02e413a6142bd1596ab852c35ddad45957efc747044c410bd7b04e9e9c1f4b8e0626c4471447f270071",
"0x8cd884d1ca3091da6084dfa5227be0cc573d3a37341c36ba1a4b2498b191b82b618b190c42ef7cf0949f38c6ba57265b",
"0x86580f91e3ce6a90f80c2ab23d3a9ae52cd19a5eca452e20ffd1e3332f120693f15275da28595c5f02dc097c3dffb217",
"0xb72dc29f39ec8e6677ee45d3e75b3646ba57d5954b5079cb525442b817b29970c721f89d8f0ad557c3eb3ba9d5648b14",
"0xb9adb4e972519f60b95ba981001b40774ee4e4447939f0e271a110290a188c24a0dcd9042bd7b2b7f6a6bbe2e7bd63cc",
"0xb130f9a7e924c7cb6abfe000fbc0ea83cfd74070878e76a011d5b8c674ca28c29a71cbaf430961d5d4e2552d33a0e313",
"0x9590cb923931b445705f9b4fc27ca474473eb01807ea789c68d7abf5e54226e7a0ec75b66b7789bd207b4b5567e5daf9",
"0xb80811d7e7bb576dadb412fd71990c4b647bbe946bc362a826fcafbfb55e3332c7615525ecd58c69d2f3f458129f5d88",
"0xb324fbad7df9c0281cccb260221d9c1e4efd32eed76c4a4fa5342d744e1bd5642f174007d4841a9e10f8eb8850385db8",
"0xa89116f74e03bd7694f1196cd8fc168bcc69c9e63aa70e78a704cb611818b254fab5b88397630b577f5a4cffb848c11f",
"0xb41d2c7b0678b6d31a64493cac2c1eb13f469cc7fb46585fe19962cec2898db4beec7222f26d0da3a67d2f4c8203e307",
"0x97753e75861780b97f27d4d6ea26d23417b4eb5be4c3e5b731480837367e02fb9835488f5365dc5ea62405aedec71369",
"0x83f18f444c843c756d76cfb4765eb38d7f89145b604fd4dc55ad02ad2ee2fde80fd1e8752e798f797653d66b9f958860",
"0xb68df85bd2417ed55d6022c2d5dfe9d0070d597c6b0dabff18cdfe646802d550be43c3ee95aed5450b4cadaaa1f2d7df",
"0xad84f26182b45ce04592e363c50b66bbc02265a4297907463f4692efb5265adb55b47158fef19cdf248964960356ca4b",
"0x84a18ebeaa1d9f89acaa4c66b4ca5e4efa643fab924272e0a3af956ad045b09a0cc4ae0e753c60845f3fd0455a8e3fd6",
"0x82d6cb2c04f8e044deef66e2e8af816b8e21da7c911aa22b63f9c494d0c9bebf2b93f35557228d841a637a4bf1533d3b",
"0xb8c6d0180c3102790cf379f081f1d0a373021f4071c13769cc969f4811e1c854bc8c7e423c98c26f4b7da57cce05e185",
"0x8949cc9effd22c232b4b21d07945c67330bb5e084fcfdf0e0b49447faba3a58e3e17d12bfb87207461a34800032f5f3e",
"0xb41b0c34be70f5dd026bf1114860d4ff4ea161785e9af054ee6c7c38f50838d35fed32c4c0d9a0ac910380ea77d191a6",
"0x981e006c0e3443e271b142b10d313457332cfd95b964876afde94740f86cba4f6dbd91746ff937363e457932495069f2",
"0x86cb5c8a1f920d57c5f4da7f5e631015c036c8826dd0788dd9837f2ae9415eaaa852217572f2b5e7d145b1765f6b3eb3",
"0x96723623c1b113ce39f918cf7b4708f140a42250272fed41e07f2a615f57106ca28162dc3f451e0c0d85e5f7ad91afab",
"0xb2406d733dab79f487a2e95a24d679850aa2d7154e1c0d50c6747d89bc02f3f52aa3daae533326d520f8274e0f880c35",
"0xb9604ef0d08f2e898ade26efa2ab6d0df159ca988974d34a2ceff0531e7e3c3b9acdd19f77338fcc6600dcedad5526f2",
"0xa55736f126c5e737c88fdb30d26fcfc9b078063087efd415f1edad91b039bae96cecbfeae2385285532edbd95f1f27a9",
"0xb9ff6cbdd93bb5f6e3f7d661ded098cc569f80763ced9b2f9e60c74731bd73ea4632aac6006e17153f8b7e4bab10aba7",
"0x8676096fa6c7479c9093a7424f2a6e637dcf93fc1ccd9b129d63d744e18e715fab9c611254cdbd15be8575ed191f04ce",
"0xb6aedd69f984a61d3f0482243d56f52461f549f075d2d1ccc04cd46983f739e36927f27b2e7b484d200ef4f6578b7c78",
"0xac3bc467cc084c697ff00105c2ae9d55fe1e92169508318c62a4c68c05141901473b7a00533713b2b574bd5a4381cfa7",
"0x81f88b8b7103f7571f65e23e214c2d6c3b044b37da7421c79d6a9d43ef51e1f0b0ccd697984d104feede6302ef2ba212",
"0x8e9dad29aa820c97cf4dded47a4a32a1772762961258253c42072c65ffc09fb84ecb2059f7fa77ac0536c2089efacac8",
"0xae4121b16427a7a605bd049daae88b93512837f5d5113ccc8d284cc8bf71dbf66d87cbd326aed2368b269b7f423ae080",
"0x9622c73b30b11692e07b95a7c219e5aa0a2007f6455350d5a7b9dc3f61f8e913c792f7c53b5e9f2bd9b1a4b99d3e1ee3",
"0x8e24420eb5401792339a36f9d120308ec10f1a4ddcfb7d9ceea205a44323ec5e48c0464915cbeeb7106dd370cfc917af",
"0x91bba3831ba25069709a7ca087e4a784fc9133cadeade3d3be3ba5bc1ece13201bc6a7f80ccbafc87490d9a1a0305e0e",
"0xb547a5676e091bd1117c8ed3e6343657d31e906d63a59e05364b0036d7a667b5303f10d3d46e8e5482b47b62cc4036d1",
"0xb432e610f1faa9fd5ac8ecb201ad2c94d04360911515e5c3c336de357a4e7a0023778896ae86459ae2b30da20d45bc54",
"0xb68c2ab1148a7ac9d99a6190ffa95f07b0d3899289284fc65069651949fe7ae1b15e3e9b7dc69f4f76b0900e19fb91b4",
"0x807dd739bb3a17694c64d729a0be67ebf4a641d946c3b748e5f03ba21d838d080636f3d0ddeb97d64e805a042114ebd0",
"0x8dd0c94eecc613d181972f7d68f20ed881bc554a5bee23933e431a3559bfc8fe6ee27b15bf74270dbc3aa9c3513edfab",
"0xb31b68f503864861cade9a774d9b726da57695819104a2cc696db8c95e5babfdfe24b2c613f8dc37d99488fd57509fa3",
"0xa05252194362b210b0f90c95c86207cd6f077a0f6bb39beed9d19fbe91702aa552e2520e4b7e204243b5fadab7867205",
"0x82f7cdba450ab0236e82e1af46055ebcd3e330d30c62abc2f1642e6681c930d589db6ac3161efeb421fe727b9a64ac67",
"0x97e7c20d3acd90bb98217ac6511e9fb032b237b7032ea43786f6f2835820eb1e6da08e02fff569a176354295b8cae69f",
"0xa7e3598a7be22d5d16dec750ce70b85b99cc3c157d162d8bc82a936ee23d8255888cb0768e8272176a45cb5d07f8e0d6",
"0xb4b6fd5bd128023043f59f4de579ecb8eec9c82e3d240a832df26dc3a37debd4348fa54c9dff1029af08d476f788f023",
"0xadc70781571a5beb7d0a41ea498a2a8a56e7bde3b1eb981efc2b40582e0f394dce279652b3ca75469ab48fc2257b0b1d",
"0xa10cd3466bf695c56c40886c70ea7e12a10284cef22a009a3eb0fdb1179b19c8102f1ae963579bca3b3535fecd3d998d",
"0xb4b8d86d95a07b64e7976c864c10f892604d68f858d75be9db333cdd75cea7f3e2067859ad5ef8abe3df2bbb92106010",
"0xa0bcb84b05db4917b2ab54e7d9e52e9ebe7c89d9c72b1fdb93ef2d74a8a26fa5961e40a3aadfffe1b6a68d9b896d0a74",
"0x95de5f528448ead998f9381c7ba8b3c4676385e2b3d6ab4e30a28e35b527909d7c9ff9284671a264b581c763d3762f89",
"0xb87fbb72b912fc64e4b93f07299f2bd19affa141780110f0165d9691a2d4088b8c0ae3ef56e17d08d71b595c27d583ab",
"0xa1dc246ec9f56457ddb9c586d290e4da3b6421f9067c710fae053d54fb58f26190144397c1eeb1491459c0996bf72849",
"0xa35f2c15c9629029115107e90c3c97c0d72d39470b94be851750cd035de64c80429cb0e04e5d0fa50f82dc76c36ef07b",
"0xb637ba26fefc8d11ce926462cec2a3fb8e8a6a5c5cc703888e856e991052befe980896ac9a2d9ccf8fd8ee45092aa72d",
"0xafc0a8ecf326b584c7538cd8b92cbca05484c9f2eeb53bfc9ea2e2eb7346e258fc62954276c4cac7e725e2266c400196",
"0xa6c61c2a24daa23a7aa36c7c82a0242b421fdd1af88fab33a58f2509a7c4cb0580ed69fb864cb24e851c56feecedd793",
"0xabe214470c1caa57eeb54541099d96cde259e75ecb01858325570ac0e2c9401490f7c1d1b3e0f24485afdad55c2cd582",
"0xa9d5177ad1a8f4c0ba3fc9258d3e77aa90a6e57d3bcfc8c0a6cc901543be661089a6659268061f1124c6fc315c289a56",
"0xb63ed1c605cd2ca6f4004b5e5fe79c9590731ce30845eb4a4bd6695b6043e19ad3684840af8e1d54766eb33314753fe9",
"0x94cd4be5bc7faeec17b643bf40481dd542d90955e6b51bca4d3128fb1e37694d3f7e3bc4ee75d6542ca24a8bad0b81d4",
"0x934a5a60b44968c0b8cc377632761b278881b421d99323439c5e707d0e106c5f72ddd33103d77600bc1b3d11f62557b7",
"0xacb14a044ff75ed7cc39f30e4e3a5c0c54b1c83c28e83cfb473fc32e386a769bf429307ba0e55a1bb2eeb06dd3d8014c",
"0x8a758539aa5a82f078763c6e45078b4e2baca3b98c018718075260ffc8e83c0642597ae604dd2063b51c83e6070d524f",
"0x8c9a7814738275922c035dcd39659514c937468af95d6d7301de4dacd9be071fd62af650a0728ab4e0a5fc53a2771865",
"0x8a4430d659cebbc1a1ec92f89c07fd6591ba55b09f625cd21ef66ef9e7390ae7a7d6c2c63ca050d8684cf05d2a5eac7f",
"0xa82330828d91dcfa446e8a0a642b82e3d4380d745e25fa740a07c687cf4bc5b660377dd287afcf93ca6471d55e8a26d6",
"0x827d4686c0f3b60bfeb16cac7e47527f4a2caa5058dc3dbc465d8e0de2537f6479cac676582fe4cde5647ec4706c9f53",
"0xad6f422e7c363c017f61dfbd9311d5b28da4fc7ae7f5c8d0cab3e15f7f12f026239ba08a04a6f9131766562361904a26",
"0x84813fd754154488ef00e2ca76d1951a5fb86be8554245419d5e19eba5eb4b9e8630765359de82f8b27eb177b73c2d40",
"0xa209756879441808476a412190c23d18492f2c581878fae2cca6a68582cbe55dc8eaf5e72b7574ba0bcd3c12d1b685c8",
"0x82b49e6ba3bd37f12063236eb7c4b7f10085551e127422fa457f6170917de1c805e197a8d1344fb8b2a6f4155ad393c3",
"0x8a74a9b04c0dc06c040ba03768a3a05dc73453fd4d5b5589ba64e1f485ac5cd44f10c71527cfd2e59b056e121688a9d4",
"0x934fe36c50ef79bfd2954b96240d1fa5407e9c17d2795db45ed1e1173064c7702710d8e722c74b95f51e0c164cdb8332",
"0x94355110229eff7e4a51c35e958cefde3de8d8d1ec17f06363a78ce8c6282278403fac5ff2c12a1857c7565fb65f68b9",
"0x87a24b2de472d97a5bb1d7729f8020c9a1c680c36f89d907a8118c6596e5f32eb9f474ff095ebb5fccb02b957b0979bc",
"0xae594a794c1668eb8356a2a7482a2513f181f2b986b792d22d785415fdaccbc4f70a13407fbd4703acefd48d4ed1e41b",
"0xa3dcce76ae232ffd62175eb1460cb8b2013df7607ba3fba680c0e62c52b30b7b8edf1c851b0c5afa8ea4156688e2a6ce",
"0xa32f076cf300be187f4afbb1d929fec74cb8c376436487c36783fa8297d7af0574447be2f7304e9cb6fa3e3e0bc89f78",
"0xb415ad89cd032270ddab84d700da9cad7e8eb28842b06ec73e82e96d740b2021953eee83d68ecc62d918c3e5ca77a3df",
"0x8fd8b27c4d90609875bddced8ed73d5a64bd258118a48793f563b7da839e3ed94a4b9570a02d7b37403acda6690085fb",
"0x91da0fa939b8a6b5b1e29cf1e9eabe3c0a9c8afd8f145187d077835aa50cb2d2ea817e0acb5c456025de0f1a1cd674cc",
"0xb587eb341325856a61809c7b3a8af866176daf5299fbd1e17b5f1fcbdda935e4d50addabc0d81b6363705a14356799a4",
"0x816b79903a2639e203fd4fa3aa29a9a7836cfc8326502364bed8de8a0c039fd0bae249716d4cf8758b822ee37cde1435",
"0xa22cb4d1e5d5a06c722a38a1c399b6a5d494ce0b8b0308e7958bea4822dbf17273dbc789944c6806fcb5669ecaa60ccd",
"0x98cf1afcd4ce98c7c839469ff82db707f02cfac06eb9c72cf54540a2d1cad6ed5a5f0698542a0fb42d22e0b5f4a3ec88",
"0x82edeb395ae976e25a4b870422e485523da63704ba6e20aff3b98d789890ac0b0da82fc9e81365d7166abf70a4aed8a8",
"0xa8a88fdf51a32af50d316aafe119b6a691e7094325a6b59094791f813cde9c7033d5cafb5d3ea635ef2f909f1b29ebdc",
"0x995b5ad21caace500daffac9ed0333d4038563be8dcc75271853bbd1a19f548780bbe1e8f2be4bc06bc0133877b3cb14",
"0xaf4b7a224fcad45141cf4eb11590225f7f182ef4d7d17c5b09de589646ece2467ba9f54616cb9996532c0a6acd545693",
"0x9257a35c42a9ce29c0be75e7fbadd6b458c3425a284f9ed5e1b2ba516ac19156e11109b9581fbc00c53ebe1230298b89",
"0x8b07e83e3b5a0885efba901fadf118fa84c0b65d4f9873c64ee0e3a28e5d9e64621449965419740b9545e2faad97a6fe",
"0xac2722717498b65093041dc75d94ea415f185827897a807ac3dcd09556cef011142a2a9326fe02ff29991a1d243598b2",
"0x95b6a0ba65d368b38852e56a2610921b0015a2551804e4369a94853f28ea0e0cce3902e349fb87b844430e66fc4ce9f7",
"0xb2a8cf6762383d86b958b09773b29ba01bf6e40439f31b7c1a157bb4c49503235a486c2e5d5d41589878a917a8c1e2e9",
"0x8d25ca690bfb7cc52903d090b35043e8027d8208d44c2dce7f0088c1e829976c7da893f2a80a43d4c85c8a30bad1c29a",
"0x86d16ce7d78031e049e6e158cde89915b61dc589b42d6ce4bd4969ebb248a37003d88e984366073d1253630ce60a1ab5",
"0x942bf83cd771a10c9bcf184da509ba0cc3dc53a1941dbd40cb710430f8d0bea53f4ee31765921ac53097bae8f3ae0b29",
"0x87e10721d75870e42e3181ae86058476f377413af8723a4d8352bf72d14663d8f9362995e244e0b6849ba8b118b9fc15",
"0x9834a7a9999068ab9ba2e9069c75d9e62f402f7aee7154870b4d543d370a1b91f70e0ba9e04af710e9b5977f9d0bfbf9",
"0x8e51f7d284eed64da1a72f3e69e4c35b0f8954637817b26a1fade27fe767dea3a0b45d2d0ebc95c27defd1958fc68aae",
"0x9815160312b5a373d360cae2506aa196f46d69904ff079be7b45437725f3bd1fee5492329d7de3a79533cad990b8b78f",
"0xad582ef0368263a1042fd552bbe03e196fe763ff9c4be7b2d2bc57c559c983b6244decd5101503ab3afc66a6c1b78e9a",
"0x8dabd55463321b05aa560ddafdcd4cbed6dfce4ad64d24d29b745b9a7be0b05dcfc75a4ecab4e4e6927e70fc533c613d",
"0xa5e327aa97fc38256435e77e946c4d0d4d0cea2288db876f12f4a644456b0e43fbed1ee1b5dcfc84687f5cf50d0aced9",
"0xad5e5e6b387e05074d06ea98c2eb33b3928f07c3d78756d33ba1ebef5545e005baddf385a090ce09eb0c1b9e073282f7",
"0xabdb15fbfd6de62b6d05fb9743813c8195ff247f42b7b55bdb1cad40e75c5da07723ed2f396695e9b7768eb0034e6e4a",
"0x8c679beebc7df210a07fca2af8bd519f53764ebba48042eb208eb3023fe601e43eb78de16c7e01ac691deb8d45a3771d",
"0xb7eaee984d4cf2f5118ab3073a305dff539f71ab2fbf278bb2381e3a7474909ddcf9c48268b887bba0ddeee8f85368aa",
"0xa32fe0ded8f04b8b01dfd5927cdc49bb4bf8f81a0feb0e9c3352e0bfbda2e0a47ebc889efa51d532395d4e1f55b12e40",
"0x910e68b8910bcf72db94d86903cb933405724d0e5471761f844d5e8032425f4cbf5edc4764cac0c97e202ff7d1260690",
"0x8635476f243d70f33199f10d5eef2c52f0dcf5cd94b0e5a1cf65bc52346b9ab5c62252ad6a30e51e641717234af1930b",
"0xb70a83bb0ea82378c69be19c7d09b9d79715f51b2f0fc76590ddb0a854676d436d5df4b5a7b07841f1e4ee0a2f53af4e",
"0xaff10fca80fa5dee07581449b5f7fc0bc49f9d3692f82c142da6a3bca473f915fa2336058b3d43ee72da68b296daed8a",
"0x8d2aaea1347fb773784fbaa1ffdb1a4532c39bebbd67a60fceac187e0c53d0c5ce24ee63b3834d37853cf2d1cfeac0eb",
"0x839095132d828e9ba44834938a2d2215c005b1b3253923ed725f967ebb9048deb4a77763551eb4f21b545a6f2a5cfc9b",
"0xad689afcde4c93de9ee7fea091a3986c18b8f150f3c5b590d01b52912eda9b500c4b6a9c859f1c94ceacc2d36a30a63c",
"0xa87f003509f48721ce96f07d6275d0271507a7c5514f43a528f5ca6b8604252c374c63b8be1243c772eb9e4fad764efc",
"0xa375b3f01167fbacc31098dc314beaac4c588e82d2b25a9f54977c416397129de684b21d623b9b8f7b662a0ebf697795",
"0xb4d9b9c4bdba5ca97778a0212ebed9eddf1a401b7b86d09aef1558c6f80923780d116429b967c470b8844aa634447609",
"0x96f222d696b5f7d78464f3935aef514fd93d92e6d2144dec0ba5d20ca92f9b47fd049b7707c489d95852198076a7b3e6",
"0x850f9dc96df38be7881d20260d4f8c1de2423893fcb07bc8f909b49efd8c7bc6279ee928dfac2d6252cbc65cea89c59b",
"0xa8c191ef703f615922a3c8f5738464baa6cad1180af9363fe5856df6f0e8f4069fbbf2459ec6a2f7335a8d938feb9025",
"0xb9c8fe68a8424710080b51e43e3a077a21fa1786a0f09b47b9f4df14e60d52364d9a7e347c9baff9064721750f90cd42",
"0x99287c61ce49700183076a705f84fde1ba9256dbe99a0cddec37a26594a0922826600d577cf06d3992e7de9e894a7aca",
"0xafe50aa8421e71aa450f69d509533599abb135d44fbe00455d0509051a8cc8b019e844cf727dd8f437d05e26e015c4e2",
"0x92a3bec46bd9bb87a1094a50e726ce45787a401f021979273cfb843f38a86f6ac30bce6a967371364d5d5fdf001f5307",
"0x94ba65a0a8f9f0f068f87286451afd3e6a2f3da3d7e04443174cdb570cbf0b1db99bdad24120342caeee94fbfa0552a1",
"0x974b5dcf98ebbafb46228637a1ee398c3232d369041b7e54acae68a7433c16597c15a421477f6084574469b234979a46",
"0x829909dc9bd18bc00f9d207265e3c5fa7cef86b263bc1a8f4bb38ac149a0d359d5826bf17873eca3e26de0003f8273b5",
"0xa412f463eb1ce3dba017e26baa1f9462540dfb1b4869f457c0662944160b15bd2048f4294516b4e5f963de2a26d8e6c2",
"0xad18c1793a0dca47a007dfeed949734c052539ebeb3038f467b13002d230b3bd3fb6a5a790bf5875fa8d7578b15eef94",
"0xa00bdccdb819cb0be1c82029845c34ad2714c98e01c8daf32742b93d6a5d68b45bd9860d2a3a16354719d94ee765c279",
"0x95bf88625608534df263f10dfd0e035e2dbc32ed364fec9ea0f54284531a1c91d9f5da40c854e6d362530922872285fb",
"0xb6129ce594f600af8204ed47ac63591df553b6b6838854d132471cce5b58b69e3a7421dc18eb9fca63d701d181fca246",
"0x88e7d4a16e184f972e9d2e37175133b73f6d7332f6d4938ba1fb2be193c3c7376a057a412d2764b11c55531c57a8bca2",
"0xb06725bd8ea964bbadee23df09862e27a72b0d84d946c27b24a89ce00bd076c072dfae6359a3d456bb6f00b58da41b9a",
"0x8a230574460af57c06daf8fe299a9e20b1814d92dfc88dbe1c2f9cd5b67554cac4615828ed8f7c04ac77631669634a2f",
"0x8c9be345ded9d3bdd3ef6ebcc914b9bad60806d5e8ed8987e644d022dead01599522d32307f4252c49fe24c609e744b9",
"0x8ed42a0222f48c01165bf89c3381b13adec7b434c8a5c11ff2401ab44e79c7c5f0ccd9a364e19499c1a4875b4ea284b5",
"0x846ee59fbeee4f2b95e8ff6b062c27383f29e8b6df3f7135526ff9bf34f4aea783924758bf83d3250c991901a11170f5",
"0xa0509b13ac31034aa9528d39cb622ffc3f74fc173a6eaf3530d6f54c0cf19eea536ac27a3296b40f0e4448f5b0415bb3",
"0xb3e24d8b7dd94c5427cb5d3fea62eedf2577fda8a3ecba5519dd57f843359e004250388fc51f3c52a4bc0e9e9add66ce",
"0x90e8fbafcd68b1c71f3fc3da14a142128a28391d4ea01e211948f742e492376fbd45bdd37ba6fe382fce28e8b50982fa",
"0xab89c7b0e1e1371751719d1e672a590fa993bbe31a0a65fcabf2967cf7b5a946e06b5a11104d3e0fe84411594f31528a",
"0x88141dd38d8a246465eb474c26f7dee2287b7269c58dd644f52809bd4458cba8e92e547ea28884a2486dcc80fb156340",
"0xb323a4bb4bfde21aec718bf4718037b0ee05c93bc7ec9ff3e7aa840b8dac931329c5aad8644a5edd0cec203767865564",
"0x912c5c064bda2983c54ebe9e927ff00eddde82e27e4cada42eaacee546e4e3b87a15ae9ae8002343b20098cca3fcda9e",
"0x949dddbaede7db7c5cfdc2801d84163a0f9a7528c795e4a68ff56289c07b432941f09d3f9325eebfb58b96cbb3be5d14",
"0xa2f1c75490027cbc691ce58257ed5e5d7d33e3ae48bc0d1c76aad9e87d180750c95dee06f24b8130ed1449ff0719f709",
"0xb121ac1f532597b043a7529f5bafaee271aca174e00ddc573f121584d42f39ba407b60909fa5d553ba3f1404e5c9efac",
"0xa227294b5ce9a703215cbc150006b8bde5383ad62d493776c3608bbd700a6a9f9266974545098467c2adf0216d0511b1",
"0xa645fc5434ef686dc1e0299cdb482b0d44a805ddddeea60f679aef6c68242f27cabb4ab6bd6950629278f1cf9a3e383b",
"0xb138f732ef5470e3a24107ab5b8914bc65df996587a1133689fce0acdaaac1ba7813e8355e1497b2f7c623376ad9d93f",
"0xb1999c40ccf40cb48fa85d2d81d3b9a23007f34979befdf0a715401e42dfdd7d17a4088c4e5148fd8fb22d93cb0715b3",
"0x95b5e4d415e128fe7267051114ae0730460b5e8b83a148f50c831354fc52fbaac8930963f7b038292f37c37564544c1b",
"0x8fd63048cf632b7d25476a7f2bd0cbe5d27a27a7cfac78b399cbf67e56cbd1950893ca3b4a9dd92478482729ccc52edd",
"0x8c0d954a41db75fd3b1277c8f39d12dad2c1bd6fa5e0c495a72d955546d69fb33aba8d735fc130b1142455369698e1f8",
"0xb85a2178b54b6cefc7c052992d1bb5697cdd17165476f843bd359d22fcb0d84536a77e805c97be806ad3983b63812ede",
"0xb85484e500f55ed2158f2ac96de79664e68049f5e595c95ff7a0d38412f1fe8065a8be66b5a093698ed5897f482bb72d",
"0xa80ea24b3b288597fd10c364b1ebcb8b49c337e9dc120692fc3554748456df57e3d9e14a29a752b53bc93f1eed6b62f7",
"0xaeae8798fdf8ef637b0d44a3fc0930683f2bffe01fba18c706792febcc591b562423f67eeacf0eed791c5f970a5066fa",
"0x864df81664bd51f7681e8849080e0ef3d7598a5c6e79aeadcf3438c4db2b37a4248d858aee0e062e3ce764be2c082d73",
"0xb8eb250f34e47f0c8fb6f0c5900c8cafcde5245e500b7366612ee6f8a11ff47764bab9514bc17f60ea39c62e7c62612d",
"0xb2d7d689319bd3c5cff190c0c378ea0aced5705a0bf949bfdf0b361f85a87f3463277100a73db4aa17292ed4f2ec73cb",
"0xa95e035c3d620207291ec6a0da8e1b4a0051168a8d4778b8d12ced8fa5eaa8341d8e2c96f7bacd2573f5b38a888530a9",
"0x8ab7a2d9b38a71dc15cdda24c9d99921da0c19dda2fb0ee5b25742a4dd6cdef98b243d25ea6cbf90d4a1a6fc622265df",
"0xa3fe8df76114613005d958e2f8477fc2557c7c00274c55db5303eb1e4d0aa1d80803fd5783e583b661c22bf1b93b1de9",
"0xabb52e7720dbddf926a96a21ef94c419d99e392962ae92b7d87a250ace37ad9c1be0192032396308108ac21878862a8d",
"0xad94073521e1583c0c8d655c7a38ee3f2490a939ca1ae814cf9a961533ca4e53b8c0bd3109782bcaec1495225ee9ef21",
"0xa2a98f2a0850b736e71a50c2e7cfe4f239627dc32fca5f2ab860926ffa7cca02a1bb40218eb88c7e30a6feff7b2ed97a",
"0xb9896126df609b9293f88683d56fc1c3731711b5ef36d51c4e8f57aa510f6d01a6de0592e0353a1430926017a8a133eb",
"0xafe0114f0991ad339c4d61e2c0cc25b78e54da66eef86251d9b94a9dacfd000667cbd7c574bf7ddaa0878ce40d9c142b",
"0xab33a9954886656307b70b710d0fd2be5aff948b018d86e0d54c1eb9bda09e1a41cf694e397cbc1140dc28544564f112",
"0x8d463548631a20b46cd84a91a1d88249d11efeb0bbc8e6512299416f50dc6826e40b102791a87feb273c3934cdae42d2",
"0x8c223a1d785dc445b5239ee13181f016594b1ba46b18b34cfb32c1886737636ff5f3c1d42e1253f54c6d7d10ae108182",
"0x85a76a1170987a00ef36737e06c219dfb14c0c6439692e4566a7e2a365e235a0409dce27bb86d6bd514c0cafdb724a84",
"0x96f66a28b87f729dd6cd50a59ed76229dd103ec05615f11c879c385ad2bbafa939d2909e70aae0294b1296e128cc9dff",
"0xb1d0ec1e38b1e5a76374cea1a8098c9ac7c5c8a2df521ae4a5cf08a2d087af6150213b6207d72f15445e71dbe4e62983",
"0x978ea82ae02c3f5b036b8dd921a6bb31de22c86851a0e9ad6ac5f841ef74539ab7495b56b463158b0d3287dbbf1efd6d",
"0x8ca3f1c70424145a58af0dc12f63e6da7039026e5af0576f9281cc45c33be931024047b0a5ac6d296494d5026a6d50af",
"0xa5634400602ab9ee917ca89930e85234a6e3efaa7184cde0963c7b5b51701f3dff3422c1ed3c9d8322b9f3d4385e625d",
"0xb9648f646c294732c2826249de9e8439c205be487c123e56e763ee49107168bd9530aea41041cea748ab4ce1ed6b8294",
"0xb72a0ee4751acad142e1bb44826ce47d11145560879db81a328cb0112b60793a29179846f157ed2df789da2a295d57fc",
"0x8f90bf84e4178b25e7f68264284ee17fd281e8a84b51a5e7ee04414170b9c637ea7c7ac80f71d02b435eb4717ccb5276",
"0xa4671ba51d40ee33dcbd4b6153197bb79919c6599043de01b4db56a1622ee9a8590f1ebecdeadc8c7105a003ea74567f",
"0x808e21a11be47cff2e04fdeac8e605c95646f1f34f6566b2c5bbfb55316da922d015c338761b20fc66e03db56a397376",
"0xa1f4d3e0838f2917ad2b3759f202b80dc76f64c31d74cd2357a4ccf90c925331bf0170704c6e323a4f9f2cc37e24fb0f",
"0xa897ac6349b9d314c600bd811112743d7e3209b2259f2df5be451a6291aee3a96017b414b3f37677c776a159766a3aa5",
"0x8b813cfc9a260895787a30cec6f764afb19b33ff0c5d99377c2079547cb4e2e9a3039273c324b96de13ba89ca32720ef",
"0xa2390c3d8a20906115440d51c467a6c844ea45d21723304d3446bf66919217b4013ca32b27342779750fd498f6f77f44",
"0x83b2d8d8160003943aa3a1b33275b36f73260b76309a1825a773746006650469027e8703f0b99237ab0c1d2c7b530b41",
"0xab8e694ecd08290e5468b2678a2b26cc4e5c7488117d046bb13cb69847aa30262ffda5999aaf62e93ef6473519f89481",
"0x92d274414b27f9792dbce29aada1e73a365fa907e46f7b71cd1cf5e8680c5edeb3b26f94be6cf6847a1e0c43408bb949",
"0x9214f625d6d9e8e81d7e5048dd448538cbbf86a49138b7e8d02a9f1ba18815bcd74ae801a118cdbac127cfbb3192201f",
"0xb81a97535bfee5394c8d555d90a96d4d99e8ed94af72281a0ce289c68c867bbc944950e082c67f2bc7e02737ba995f73",
"0x96f3652f6cf5578065d7d873538e3f245ba912d7e8d5881228ed6fbad0d2bf737130b6f353a2a1ad224961321d117731",
"0x850296b95e28e9b8a2dbc7078d5a7b49ac1f18009cd19854a26b76837fa15a38d9ec8563feef30412676b0506754e5a3",
"0xb4a59fea80d8026b1a36e7c06acb8d9903430beea306b1f3c4f049a97287e552f35a512c1b0205e5a09010dd7648ae03",
"0xb689fe66eca8424246b13d637ee230b0d7df53e7d7d3d30abbbd0ba68129c4bdba7a19291a0757f87a360e3676e1d0c1",
"0x9411006414a816076fba27262b18e0972aea1fb382f702e1769b4b723300e19ffaa7c30fed6f85cb453227aa9b60e57f",
"0x96727fcbbba02678d8ab6efdecbc1520a4948d216bfe6bad5a9b7ca671e432ca32d78eac06c15a751686a8e2c8cadd90",
"0x8629d173772ec13f79f32ad6d8f44b1f0193005fa790153dbd664d82551ca61578fc469cd2e402fd73dffd0f484a0734",
"0x974e667759f8f80cd175fde904721f18462a0e41933e2190c22b71148dc8f3a9226b238c065db39dc1c16ba38cacd4a2",
"0xa923b9cab201c92772f8a5f24dcfff38e6a64f3e7bce98fb4c07c501cf417486ee351cb3a1178d22e774eefc7cca43a2",
"0xafe9ec7ab9d38ed55d9a4ba7b61e6af7d04afadc148eed00fb649d49e1a93d8c4540d5c4abda067b932e471355a529e4",
"0x863da460ec5ce9d0576ec531f61702ba57eaf143424657ccbc3f10d979938b85037d1b210745461c2d8e259fca8199b2",
"0x805d3cea10c354758028de971de4d2c8aed3fd79c8735cc2e2c9194a72efe631ea42cc0e9c7dd1b8b49c096f9a6a6fdb",
"0x8bbf7d3aa5647ddafdf04c1eface154818fb8b5629617476b1d4dc834e1ed6cd74cd4fc11a10d7610f49e40530d47698",
"0x8c82b6491d918c0a1afd611d98774fad2a76c7a0a227fbadc3286144d6d120e1cf257496a0acccdb3ca0c94727311b01",
"0xb643e6baba0a8a1ae1fcb92ade166d6890f6dc3350a9a01ae078ef9b4d97cffc1419b37dcdf509d6e61606373d1c9475",
"0x8861fe2272a789ddfb2555431acbc71f67f942fe395a2c975843030e50961372a24ccda34fd48e5f6f640c50dc065a6e",
"0x97025de3263b266180219aeb308ca71f1fed55f54ac50c87b4b8c8b1c1b96c51ffc53b75faf37b2a8627c819a1943771",
"0x983d19931b7a2116fbb3bd429b9e5def5e6b7e8750d5faabc83446e8b032f5228af0620c84dbf51c498cbcc8aba43ec2",
"0x8bba98fcc07c2c43d416a7d4d1eca067dba76230535bd92be46e3fe7b3d5743d2998b822947f3fdc0bea3b2ad9d9ba93",
"0x8422722318b72285f7d86025468dec58f9a0f301a29a09df5277c99f88cb6ed028df5c868dc1e584366415c647a47ec9",
"0xa338f8183b6939c5c1681a4b32a5bc81f2036d3bd14eb223aeb6a0e7b6c63be76573042717b68733375403a3bd6d0834",
"0x91ce651c3ecdd672c051775dff4ad3efd12d81c7510398c723fe13620f0f9f382d9c6e47e8bf9a54e8da9e1d71a7d010",
"0x83269cad7d0ed41160fec4be634b6e9c0d8830bb95b153f68f6e1e0b509a0608400c8ddebbad116927982101950cdeab",
"0x80cce1262b8cff5025c364a00786c6c08ccfa363de9a6c893161d79b420485f3e45fc47dc9625610ee94ad5fde5a672b",
"0x90afb5e96929fb0d4f908b74793f76bbcc95e2a23042cbf9bacb2e39e1708dc8dfb331832e4255e2c166aec99e3bf357",
"0xb743ff215b97288517332a9829e1f79ebb24407f696f9b157003bc9945b460e9a4dae0f5fa3941ac18f945a678157b2e",
"0xb6542332bb5d13a200de1759bf46f50d150ffd3dcd896803219c95a765f3d57ec28563fd9c1782faeb0a5684675d51bd",
"0x94b7e98d8ad0cdb1a7e0ed465f05d285087f190cf9ea9e839687a6030cead4864abcc0d44343e1670a5a1d6dcc27a2be",
"0xaa6895a135a4efb439fb09e216d64594304fd15430a2f81de89bcd10ceeadcfbbdb9c481e846a258df0c45c81b3fd02c",
"0x9826c9769c497004a35fcd31f58e9237319e642ed928ff49749458511bb946dadecacae5a2e8b5d90a5857621588de94",
"0xb0ae1b709f9f52254bfc25520ef18f2ffccb80a564e7400e90e0e348c53631f13e29196b16c53fff25c6a4df90280086",
"0x8b97eeb339ec833536f7bfa2b0bfc1720ba2d9f8ebfd5cb4173d1aef853b911a4cb6cd73bd9a6576af3c67f79692db06",
"0xa9a785d59414a40cc025d35b1aae511fde18e8360e06be1d60f1cba87bb3879396728a448ffe8a2689b5523a0313fa21",
"0xa551c402c0456b348c81fce4d2e05d4a56c37591342e80f35885fdc766cb0df5f9a14b7a8178d105ed6a1620b04a8fe8",
"0x9653da9cb83b2869dc60a2de6bbaeaa670fa6516a92ca1da09a6b36fde1f41387d13aa013b2cfb05fb0dd568d00d48c2",
"0x8b1f88f326228ab4ea19c28ffae9e93a192c778aee51538fa09a80f6ed93b67613c5e1e7dc7bbefb7dc540a094bf160c",
"0x92ba10d87689d6f13ce6863071bcb5b778fb82000d8781a64f3e67af1ec2b2d50cdfd85d5341ebc3593e764e9a74e43f",
"0x85845552d41fd0d36d42069a50cbfe61520d3b604fc0c305eacc9a1f37b9f65a57a1e833cd3ef3979db057779a4ebf93",
"0x8654fbee8a4b281fe61787f516e4212ec5c93dd6c93736097782d66acc81f05c547d8304f041c6c2cb3b3d5b277c5545",
"0x8bf521b5eca84a18afb6926e1c5c143ab64d3822cbba3b524339940eb628e1f5296fc938f27276bfb8f8ecbfbb730b5e",
"0xa20ab3d19b03c55719a4507d401003767f7fd9593c2b2d11724aa83325d6a226a687ca63f698b29849aabce8f0ee610e",
"0x8bfca091ed9b8dc24079cfed213b6eaec21b0dd9a22a77e9757e441dde14a023ee2e7a6cb913b21804df682fc420cb06",
"0x8d5bdb82757a373d8653ce5e12e6246ab606fa7ca23688d63597c964876b92a9c100cd45bf9fa4bd64556634d3fbdbcb",
"0xb3faa4919395957683785762d2bb45ce36be9c19f348d8fe7b772e920a56bd3b1a89efb552c85cf17020d630f2af6575",
"0xa9ad7dfe24c1b5aa0d79b7ff80d336f74d64b16bc3602b22e7536a6bcd6d56ef685f7d1773cd70f055e2cfdf4ead2043",
"0xaf329f026ad345ba2ba781e8db7f908d111097eb028acc1536a5f8d6bb03b26433c7a740d5290862c1e2b10451e9162d",
"0x8c1b9bed1a2371fb5340a7f8c721df731b7835dc43ca0cf9aba0d644cbc22a9e223016fa8b52bd4f68ec8ebf4684f435",
"0xb0297e64424e87bca6ea91b60be5d8cf09fd05ee5dff1b31fcdb4e966e520ad2dff7ff9d316dcf7b7225b3ca26a8f317",
"0xa4d6eb27235d0d846fda81fe5085d06f1b28b93b0ab44e34fcdb93d6936d010abf8addcf301a3fc914ef71b7992faa80",
"0xa7b5ce82076a9a3354ebf4ab32c50c96d890d90b4ad60635e7798c69f286ddf897f8941c8faf6398754eebd14e3503c2",
"0x8d3a33fdb641fcd7c22bb5f58b20f1c58267bb90517113aef86269cc9decfb0e2d921e91e31ea11904224c8ac26d851e",
"0xb56b934e822be5aa3ef7c728918f767d4b624117bcfd6f987f9e53e1c18b956bdf7a2eed8afd49169d5112f8222946eb",
"0xad6685c0a17876cee8284f62fbfec96d2971bcc55d1b6cd19848c61d2dc2655f016473277aedc6a94d08ccecd9e7f5bb",
"0xac9d9b9946f92d9167818bc2f400af335c50b55499f8642f2d95c5e90578fa4958c1181d098b08c064c14288722dcede",
"0xa9a25f1132912b4a1974cea2227935196e9913b355869d6b7eee8f6f443fe849ef54e5dc76fe18dc31603633302d06d4",
"0xb18c43914eaa3eac7e92f72f4ca5f33c5cdc959204cf848db3999babe1a4112dcc196e1c81ca14050261d3ee654ed8a1",
"0xaa57c9bd255e19930330e67eddbbda30a210f1f991471942d4e4ff9e3f51173b405769102064f756a8886ebaf8a903ad",
"0x8dbf109fb26a12bde88555d3bd287e3268dd9d6ddb93f220952ee294897a1c128bbae877f35d5d1861e04c576dc2a3d7",
"0xb276ca76d6a75fc348d8f10da0ed3fc4022d9c3d3788ac1741e861d7d3cd8ed911dbd9a4ea5d88b478d2f5c439ddc138",
"0xad3c8b4ff9e7af7fdae7bd6653d03db1d226ae62a7c2f74f6b2832c7ffaca6c8d90bbb833f4cce2d336dbcd88e4e7edb",
"0x8f6df239c7023257bda104e19b05e7671c31ef5b952344176a5dc249e7b0dadfff969eb79fcd2eb62f722a6611d26661",
"0x96c86fa69c51fea17a4b0b740b635d21d8d9f1ec94c8925924eb1621f1f5b2cd23431da9cb4e32cd70f53684ca931966",
"0x9741ad36427760715378550d1c22de3d2bc438033cc03c3200021d16f3c426f4d58606f83fe3f93ef3c88b8402c4887e",
"0x8305d3e5e2be2bab1072acf6cbb50d431bc41547feff297cabeb44ee44bde2c3c5864220e5af6c591bce6ce16b1aaf84",
"0x9374420e4f1f23af9ac3b0b5ad9de1f41f1725765d2e2f398b152e145f3f4664e2839eadf1ba2fadf141b48c92141337",
"0xa4562968152d6d24aea9fbcb0978c3c70e25e2236e08fb2b4557e5b11ab9d8cb33e1ad502e9d972ff00a4db52f8f7d35",
"0x83c93a5f9ee1698e45e3f264c8fe9eb2082e9d977cdbaee4c035205285b7451d1c4462edab69c290d4a7178232ef8d3e",
"0xa54576136787045261d6d95aa9e53e2668c8094027a12391178cdc2dfaef94fb729b3dc4dc24a3a0b69a94249d6f37b2",
"0x88b172a1e267f88f0e55763653a1f69479654b9a932bbb4ef8ee33533e27b35d8182a8009ca0e2f13dd14302c91d0d5a",
"0xb74d49c1fcea755cd747e6f05b890df9f93ceafe6a09bcd5699a1cb6bd4b32a886c875e6bc7923a37cbc6998214f23b6",
"0xb1501a459562a0c3592dbad1ead82252853e1975cb186c78e2b1ce604ecedc8deba613e903626b8064da7887de5c7e4f",
"0x95f9ba4f84db2b875de1ee1dd7840d663759a6faf20dab17e85f11aef9a403db820dbfdc41fcac8e76799f40a881b3a8",
"0x85faa3344f8d07de0d74bc7205082693fe3d1968e0c40310a48ab1773c0ff9b2f61c75eff74166dd63ce8c2a9c0aa519",
"0x98c668be65f12afcf6f45e360fa9ef6d05e6eb63ac8132c95038f6730e585e34e25aada3cb433dc028ad477a7d137358",
"0x975a911ed97d747099f609fd19a76c00f5ea005d50b96376ab75c2bef96162ea338dd0e9b8868bcb2b6f1eda525cb591",
"0x98f1b6264ebd9954b01f058026489763421a01a0889f37bd1acd6785ccf44290b46172635a0126e16bf997d1804792fd",
"0x980f1b1f5bdf3a2813aecaffd057f4fbdfcd2323fb5860262f0c4a79c525df63784c643f20eb22ab9b14ba15c7b01615",
"0x8436b25525dc8e6094322bdf807d1aa2996e64091c30a9e12173a1b357b95b1ba938aebe1fe55c2758edf8e34b5f7329",
"0x92cb8d7f898742bb4e2b6ed8e55fcac5382b99a67fc1e5ff6dc91db1e4d7c3c1285ca6e80932d9d9513ce0e21654e372",
"0x8f2b6a3807e21de3b82c754927c4359e7c700286e88955472de3c3ef91f58994ea56a10837b96576b9beff1f996833db",
"0x9568497114a28ebb94d365a73e54d296c78c8a00e400148afb9d408684c7a03111eddeba638cc0e2039f1f45c835ef4d",
"0xaad3c72b4fe19411651a684e54ad95b2650b75889ce24e5bf1af5d0b63a7b3405e10684b4247b95de6c67644c6f813b8",
"0xad70bd1662ba8de25a894ed9d369f6982c05560bf5e369c2d01968a226cf5c038eda615c38eb0fc9d0ad7bd5f5c31b8d",
"0x91c1c7f59a561c6afc0c440375573f491998d7ba0d1c5837860b2074370c0697182de098990861bc8921add91adfa84e",
"0xb5f1221b37e19ab5b0be66ce4fcf8d8edb08b31af055ae3df2bc3d3b6547da5064ab9a86856853faf0006702bccc9e37",
"0xac0c6a10ddc41f1500e8efe23a6472309ce2ea4cca644421f3a6a6c9e19460a06c25a2f66116c16eb078863a3bcdbcde",
"0x93f8204ae13e2c7b92d784dafeb28d6ceb2c1cc6fe0caab039e5a378bc87c992e3cbea64347588991a45dd91ad52308f",
"0xace84ae505573b433fbc0a0f93a86f71f48f30b334fec5fd86f0fca0578080cce605955e3f244bd2901a9ab2fb9855ae",
"0x8c47430b02a01f7efdc90446043592552d2436c663f62dd0415a111ab41124271529f40b78a583e946fc8a791bb405e2",
"0x9746e09ca817dd9257ee1ac4ba95dce1f1da66dc07172b7e7859f173c40b21f0f912f9273398a7b050c186c92f1baef7",
"0x8e8cd42957e12c38377103dfc2c3013deb627daaf6d6011073245f6bff3c4b8c27ec5aa7d455c5f00eb295398ca9bc58",
"0xa5936633ed28b07ccc8a5434babbfe7a2111b8bb2c306e6fe388ad315ea19641ea7515f358fc667f13d9fb1d1067eb25",
"0x8914bd61c2063292ddd9b64305e2a7d8883cdb91dee547a128577de553f1b611d4a5bfe17e4969cabbf38bd7a5d4fc14",
"0x85de28eb89efa5c419b126ce3287936ab9ea09eeedf974bc37ad6a7bff17547ede5afe428cbdac3ba2fd959b3d16fbcd",
"0xb622a192754e8fa73d69454e4f6f89eefc6bba3716b0082fdf74bda4847d8bd21c3e028a61c3510c2d1b25ee9c48401b",
"0xa74c9366e7a9cf8cedf66359b3b03b4b0dd2fe4c15bf4b1f728be7472a27ce479e0d92fd5cbe50363b1245c45b144ffa",
"0x8870152d25d02c8b580646372232ac065eaf7aa3890d6acf5bc9d069f98251cdfab88c2c02e11a00b7f51c8d55f70b29",
"0xa394a4676432f6a9cc8c66ba6e2b3d29881373312d41128413a0df486a793d4b9f1e6d3a7ae5e1a407a789dd1fab29ba",
"0xb8e5aa58a256172195c149fffe7513223e0412eca319e344ea06b76ff3913ba9ec25eef2555ccad2c1e35c7947ae3e47",
"0xb8d4e3185eb5ef45f3502b08acbf879dcf087f7b3cbda3214d612053954e63cdb6fb3512f84ac6e94363445bd2e8cea8",
"0x98521d27aa04a53db1f0d19783e8c78a4650e41ca8c046d075410724201be5054ac163de6e23f7871055a7a5c754313e",
"0x858b814fc3e5d2eededace3d725437b346a82caa4e14d7afca6b8abf3a852417ff731c5746cc88389a9daa4645316869",
"0xaa43f1d635a80a8d383e046433552cbb170cf4b659e660abb397d71e33225552be0939daf1ff0478607e1d6f4b84f604",
"0x8f1e55b08dd72da3daf10631f32df89fb0ff52ab9871024412cfc5ff65ff6b2afd33f03ab713a22fef5a8fa798634354",
"0xa478b0fd88abcdd3da4e63fb669f88c3fb2b7b0750385f9ed520637504ed4c5764ad5c0f0b2d843a0ad99097507a5d0e",
"0xb9b7ecccf5140d3b4660e79625eeab5bb97f95cb78c1e310d4e234911eb4952d467d7b163a991d519f311a0138f04b8b",
"0xa597c486699b203b81ad5a9ad239aaf796391f26d393241e691ebeb8ea5b102bddf56ea2a356b354f0187bc7d4b33689",
"0x8ed9a55edda0a1414bd64a6069f0ec07f20b3ea7a4dd7eefca79082bcd5f5f94ab145ab84eecb2d5029964fb59cd4eac",
"0x8ffd7c21db22cc010d3f172f924c463afa9227f0b8009d0a1be6bd3859319de6fc8c933e1b2e7158b168916d13529110",
"0x86e8e6143a552696dbb1349a212ece4408b8efd54d7fbffe35178c13b8fde9ff70411a63fe4a8be35b9fcb282922b538",
"0x9553f39a170425b4a4e346af3937bec3be30730e446fd387910fd7d64b04d4aeda0de6c35c973ea0d5e9d60397897f9d",
"0xb53251df22419f7dc65242488e2f38e0e01aa60412eaa5123e4067ac5d76f20e72d093a838f91539eacfb1c9086a46f7",
"0xb549a2984990b3a159b6be06d59c158f43b69cf8acabc035572e1d15c3255c51f8a0f3df788cdfd244e878c055f25ac9",
"0xa615712ae4d4fd3ef9723fa09988a44b677beb098fce85bf4ea39a943cc027ba2547ee5ec4143b5f00011916a5f358db",
"0x981f4afdc7ca36475fc567de39d62ad202a93350c7b9d09da59dbc1f10d53ef0f9c7095b3a2ac2c86af71938b895cfc5",
"0xa8f096db03c9e1e6675bbccb9a26bd202cbea463f9557e3de5b6e08aa15fdc2345a898656fb9077fb32fa30ea69a0e7b",
"0x91fae7166f0f4d21e7fba3f7737a7d88b38d472de99dbd61a6b26ec935bba0da282d8b8b9b2c94e7bdf2003b5d354931",
"0x8d6354c49b46d287d27c228f7935de94848a2eea7e0ba3d69e24f4569fdc400be68d829e6579b4b39f36cd44cb382dcc",
"0xa18219917297c3cfd8341e0b434780b541f19b59f8e0044b42c02b333a5f56441b84db7ae94955a06fa14a0cea39bad7",
"0xa72f04ca6876fad40e71fdc530f47483ca634710a8d7b0a26dc31bd4ab9c505860f31891e66ceb0446f2418866c800cf",
"0x949eea840f5c350414b8ee59504a3d00b1bcaac456c2e764ac6d12b0ead715f46b10a06d18815942c4c4785e0731a132",
"0x9470729bf237acfbcc5df5cb1b843e65db9e9624989a0844857ed40d7514680118acf0eb37520bfc1477232d5aa7fcdf",
"0xac92af39f920851830d1cc3bda7753ceb80c66488c8e78c047d16409771fdb267079d31480e1cc908610da8d23b30741",
"0xb3cc1b562b5aaa402a33edb28d6154d88692e07ef141e5ea03a39dac3fd2fb4de054214f96a6afc78343549fbcea6572",
"0x906908bc424c967b8cf3e982535c62f3fe3d91076f583b5e0f027b5a818a0dfda7d0e46855d4649d598a3985fb4b855a",
"0x97742b33610964be65e27ab4f91086559cec5844b1a4ccd15bec47146fe016bbeddf202584e3de33e683a0a16f9ec7ab",
"0xac9a17e15a1a280d165746f68581fa62cf4fc93d4d20d95c19de9910c4ff223e0c4d1659e4b3576d6ac0486d75531610",
"0x8eb44f5cd5f5681d2c1b0a702ec60d2ffaea1c67e995d1db91f6877d07f5215d057e22d8faa38bb30165588785da22bf",
"0xafbc3418dbcf3ed2de7f3a8ea61e545386af1864fafc769d35f41bce9ceb6084b7a5645b3c14dd275fc1fa99313a7847",
"0xa1e6d4569fa4c7e907394511063119e952efffd6133e68f6910ab98959aed3e62179561a2516a2260ef6e87c82b4291c",
"0xb90e66fac83bd5d155c25a2b0fad4d26648149f4a0a03165b50679286f9cad05241087b2eb2e3be67fec4259c8834ef8",
"0x8c37efd274ab046bae70c51b89617b98e3f516a95ecd65f26bcfef889d417f83534d2ecfacce8049b78a7f742cfa7e70",
"0x9904023b35c02512c87eb215e13bb2951d099f31de97a3f875ad980d698b5df67fc075b452f6b651ccab46e528bea1a0",
"0xb877e7cfc2fd3c423cf314112ec6b46a46387280ad83643eedd5099684fea54f6add99a6007d744af57fcf8792fe71bf",
"0x9781d4b1026ec1b492a245957b1f3cafbaae598e19a52a84df68d66c34fffb0e26e2396dd33fa6923f93ee424cb328f7",
"0x9309d974712b4f7eee4149edd215f5a3797b2e2f5a470274de67a9cc84284273af55298d2720a1d1551da2034192bc7a",
"0x8f69ecdacc5b69f51b7c86c4034535cecf71325b4ee701d1df7386558cb22dbed2f0b0ea46708fe7299edfd87ad538e8",
"0x87bbd9dbb43e5f563efe01f5078dc41f11649649959513272db71cf7ecd2fcd56505ad41662f176834c689c575c83771",
"0xb62c985584ff5e6f37e4e3df1ed689af83cfbb8e1cb84512ce9c86cd3feeefb03cc7d8cc95626ad4d4401dd8cc7cbb4b",
],
};
export const BEACON_SYNC_SUPER_MAJORITY = Math.ceil(
(BEACON_SYNC_COMMITTEE_SIZE * 2) / 3
);
// These are the rough numbers from benchmark experiments
export const DEFAULT_BATCH_SIZE = 200;
export const DEFAULT_TREE_DEGREE = 200;
export const POLLING_DELAY = 13 * 1000; //13s (slightly higher than the slot time)

5
src/client/index.ts Normal file
View File

@ -0,0 +1,5 @@
import Client from "./client.js";
import Prover from "./prover.js";
export { Client, Prover };
export { VerifyingProvider } from "./rpc/index.js";

11
src/client/interfaces.ts Normal file
View File

@ -0,0 +1,11 @@
import { AsyncOrSync } from "ts-essentials";
import { LightClientUpdate } from "./types.js";
export interface IProver {
get callback(): Function;
getSyncUpdate(
period: number,
currentPeriod: number,
cacheCount: number
): AsyncOrSync<LightClientUpdate>;
}

42
src/client/prover.ts Normal file
View File

@ -0,0 +1,42 @@
import * as altair from "@lodestar/types/altair";
import { IProver } from "./interfaces.js";
import { LightClientUpdate } from "./types.js";
export default class Prover implements IProver {
cachedSyncUpdate: Map<number, LightClientUpdate> = new Map();
constructor(callback: Function) {
this._callback = callback;
}
private _callback: Function;
get callback(): Function {
return this._callback;
}
async _getSyncUpdates(
startPeriod: number,
maxCount: number
): Promise<LightClientUpdate[]> {
const res = await this._callback(
`/eth/v1/beacon/light_client/updates?start_period=${startPeriod}&count=${maxCount}`
);
return res.map((u: any) => altair.ssz.LightClientUpdate.fromJson(u.data));
}
async getSyncUpdate(
period: number,
currentPeriod: number,
cacheCount: number
): Promise<LightClientUpdate> {
const _cacheCount = Math.min(currentPeriod - period + 1, cacheCount);
if (!this.cachedSyncUpdate.has(period)) {
const vals = await this._getSyncUpdates(period, _cacheCount);
for (let i = 0; i < _cacheCount; i++) {
this.cachedSyncUpdate.set(period + i, vals[i]);
}
}
return this.cachedSyncUpdate.get(period)!;
}
}

View File

@ -0,0 +1,10 @@
export const ZERO_ADDR = '0x0000000000000000000000000000000000000000';
// TODO: set the correct gas limit!
export const GAS_LIMIT = '0x1c9c380';
export const REQUEST_BATCH_SIZE = 10;
export const MAX_SOCKET = 10;
export const EMPTY_ACCOUNT_EXTCODEHASH =
'0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
export const MAX_BLOCK_HISTORY = BigInt(256);
export const MAX_BLOCK_FUTURE = BigInt(3);
export const DEFAULT_BLOCK_PARAMETER = 'latest';

13
src/client/rpc/errors.ts Normal file
View File

@ -0,0 +1,13 @@
import { JSONRPCErrorCode, JSONRPCErrorException } from 'json-rpc-2.0';
export class InternalError extends JSONRPCErrorException {
constructor(message: string) {
super(message, JSONRPCErrorCode.InternalError);
}
}
export class InvalidParamsError extends JSONRPCErrorException {
constructor(message: string) {
super(message, JSONRPCErrorCode.InvalidParams);
}
}

1
src/client/rpc/index.ts Normal file
View File

@ -0,0 +1 @@
export { VerifyingProvider } from "./provider.js";

699
src/client/rpc/provider.ts Normal file
View File

@ -0,0 +1,699 @@
import _ from "lodash";
import { Trie } from "@ethereumjs/trie";
import rlp from "rlp";
import { Common, Chain } from "@ethereumjs/common";
import {
Address,
Account,
toType,
bufferToHex,
toBuffer,
TypeOutput,
setLengthLeft,
KECCAK256_NULL_S,
} from "@ethereumjs/util";
import { VM } from "@ethereumjs/vm";
import { BlockHeader, Block } from "@ethereumjs/block";
import { Blockchain } from "@ethereumjs/blockchain";
import { TransactionFactory } from "@ethereumjs/tx";
import {
AddressHex,
Bytes32,
RPCTx,
AccountResponse,
CodeResponse,
Bytes,
BlockNumber as BlockOpt,
HexString,
JSONRPCReceipt,
AccessList,
GetProof,
} from "./types.js";
import {
ZERO_ADDR,
MAX_BLOCK_HISTORY,
MAX_BLOCK_FUTURE,
DEFAULT_BLOCK_PARAMETER,
} from "./constants.js";
import { headerDataFromWeb3Response, blockDataFromWeb3Response } from "./utils";
import { keccak256 } from "ethers";
import { InternalError, InvalidParamsError } from "./errors.js";
import { RPC } from "./rpc.js";
const bigIntToHex = (n: string | bigint | number): string =>
"0x" + BigInt(n).toString(16);
const emptyAccountSerialize = new Account().serialize();
export class VerifyingProvider {
private rpc;
common: Common;
vm: VM | null = null;
private blockHashes: { [blockNumberHex: string]: Bytes32 } = {};
private blockPromises: {
[blockNumberHex: string]: { promise: Promise<void>; resolve: () => void };
} = {};
private blockHeaders: { [blockHash: string]: BlockHeader } = {};
private latestBlockNumber: bigint;
private _methods: Map<string, Function> = new Map<string, Function>(
Object.entries({
eth_getBalance: this.getBalance,
eth_blockNumber: this.blockNumber,
eth_chainId: this.chainId,
eth_getCode: this.getCode,
eth_getTransactionCount: this.getTransactionCount,
eth_call: this.call,
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
) {
this.rpc = new RPC(rpcCallback);
this.common = new Common({
chain,
});
const _blockNumber = BigInt(blockNumber);
this.latestBlockNumber = _blockNumber;
this.blockHashes[bigIntToHex(_blockNumber)] = blockHash;
}
update(blockHash: Bytes32, blockNumber: bigint) {
const blockNumberHex = bigIntToHex(blockNumber);
if (
blockNumberHex in this.blockHashes &&
this.blockHashes[blockNumberHex] !== blockHash
) {
console.log(
"Overriding an existing verified blockhash. Possibly the chain had a reorg"
);
}
const latestBlockNumber = this.latestBlockNumber;
this.latestBlockNumber = blockNumber;
this.blockHashes[blockNumberHex] = blockHash;
if (blockNumber > latestBlockNumber) {
for (let b = latestBlockNumber + BigInt(1); b <= blockNumber; b++) {
const bHex = bigIntToHex(b);
if (bHex in this.blockPromises) {
this.blockPromises[bHex].resolve();
}
}
}
}
public async rpcMethod(method: string, params: any) {
if (this._methods.has(method)) {
return this._methods.get(method)?.bind(this)(...params);
}
throw new Error("method not found");
}
private async getBalance(
addressHex: AddressHex,
blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER
) {
const header = await this.getBlockHeader(blockOpt);
const address = Address.fromString(addressHex);
const { result: proof, success } = await this.rpc.request({
method: "eth_getProof",
params: [addressHex, [], bigIntToHex(header.number)],
});
if (!success) {
throw new InternalError(`RPC request failed`);
}
const isAccountCorrect = await this.verifyProof(
address,
[],
header.stateRoot,
proof
);
if (!isAccountCorrect) {
throw new InternalError("Invalid account proof provided by the RPC");
}
return bigIntToHex(proof.balance);
}
private async blockNumber(): Promise<HexString> {
return bigIntToHex(this.latestBlockNumber);
}
private async chainId(): Promise<HexString> {
return bigIntToHex(this.common.chainId());
}
private async getCode(
addressHex: AddressHex,
blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER
): Promise<HexString> {
const header = await this.getBlockHeader(blockOpt);
const res = await this.rpc.requestBatch([
{
method: "eth_getProof",
params: [addressHex, [], bigIntToHex(header.number)],
},
{
method: "eth_getCode",
params: [addressHex, bigIntToHex(header.number)],
},
]);
if (res.some((r) => !r.success)) {
throw new InternalError(`RPC request failed`);
}
const [accountProof, code] = [res[0].result, res[1].result];
const address = Address.fromString(addressHex);
const isAccountCorrect = await this.verifyProof(
address,
[],
header.stateRoot,
accountProof
);
if (!isAccountCorrect) {
throw new InternalError(`invalid account proof provided by the RPC`);
}
const isCodeCorrect = await this.verifyCodeHash(
code,
accountProof.codeHash
);
if (!isCodeCorrect) {
throw new InternalError(
`code provided by the RPC doesn't match the account's codeHash`
);
}
return code;
}
private async getTransactionCount(
addressHex: AddressHex,
blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER
): Promise<HexString> {
const header = await this.getBlockHeader(blockOpt);
const address = Address.fromString(addressHex);
const { result: proof, success } = await this.rpc.request({
method: "eth_getProof",
params: [addressHex, [], bigIntToHex(header.number)],
});
if (!success) {
throw new InternalError(`RPC request failed`);
}
const isAccountCorrect = await this.verifyProof(
address,
[],
header.stateRoot,
proof
);
if (!isAccountCorrect) {
throw new InternalError(`invalid account proof provided by the RPC`);
}
return bigIntToHex(proof.nonce.toString());
}
private async call(
transaction: RPCTx,
blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER
) {
try {
this.validateTx(transaction);
} catch (e: any) {
throw new InvalidParamsError((e as Error).message);
}
const header = await this.getBlockHeader(blockOpt);
const vm = await this.getVM(transaction, header);
const {
from,
to,
gas: gasLimit,
gasPrice,
maxPriorityFeePerGas,
value,
data,
} = transaction;
try {
const runCallOpts = {
caller: from ? Address.fromString(from) : undefined,
to: to ? Address.fromString(to) : undefined,
gasLimit: toType(gasLimit, TypeOutput.BigInt),
gasPrice: toType(gasPrice || maxPriorityFeePerGas, TypeOutput.BigInt),
value: toType(value, TypeOutput.BigInt),
data: data ? toBuffer(data) : undefined,
block: { header },
};
const { execResult } = await vm.evm.runCall(runCallOpts);
return bufferToHex(execResult.returnValue);
} catch (error: any) {
throw new InternalError(error.message.toString());
}
}
private async estimateGas(
transaction: RPCTx,
blockOpt: BlockOpt = DEFAULT_BLOCK_PARAMETER
) {
try {
this.validateTx(transaction);
} catch (e) {
throw new InvalidParamsError((e as Error).message);
}
const header = await this.getBlockHeader(blockOpt);
if (transaction.gas == undefined) {
// If no gas limit is specified use the last block gas limit as an upper bound.
transaction.gas = bigIntToHex(header.gasLimit);
}
const txType = BigInt(
transaction.maxFeePerGas || transaction.maxPriorityFeePerGas
? 2
: transaction.accessList
? 1
: 0
);
if (txType == BigInt(2)) {
transaction.maxFeePerGas =
transaction.maxFeePerGas || bigIntToHex(header.baseFeePerGas!);
} else {
if (
transaction.gasPrice == undefined ||
BigInt(transaction.gasPrice) === BigInt(0)
) {
transaction.gasPrice = bigIntToHex(header.baseFeePerGas!);
}
}
const txData = {
...transaction,
type: bigIntToHex(txType),
gasLimit: transaction.gas,
};
const tx = TransactionFactory.fromTxData(txData, {
common: this.common,
freeze: false,
});
const vm = await this.getVM(transaction, header);
// set from address
const from = transaction.from
? Address.fromString(transaction.from)
: Address.zero();
tx.getSenderAddress = () => {
return from;
};
try {
const { totalGasSpent } = await vm.runTx({
tx,
skipNonce: true,
skipBalance: true,
skipBlockGasLimitValidation: true,
block: { header } as any,
});
return bigIntToHex(totalGasSpent);
} catch (error: any) {
throw new InternalError(error.message.toString());
}
}
private async sendRawTransaction(signedTx: string): Promise<string> {
// TODO: brodcast tx directly to the mem pool?
const { success } = await this.rpc.request({
method: "eth_sendRawTransaction",
params: [signedTx],
});
if (!success) {
throw new InternalError(`RPC request failed`);
}
const tx = TransactionFactory.fromSerializedData(toBuffer(signedTx), {
common: this.common,
});
return bufferToHex(tx.hash());
}
private async getTransactionReceipt(
txHash: Bytes32
): Promise<JSONRPCReceipt | null> {
const { result: receipt, success } = await this.rpc.request({
method: "eth_getTransactionReceipt",
params: [txHash],
});
if (!(success && receipt)) {
return null;
}
const header = await this.getBlockHeader(receipt.blockNumber);
const block = await this.getBlock(header);
const index = block.transactions.findIndex(
(tx) => bufferToHex(tx.hash()) === txHash.toLowerCase()
);
if (index === -1) {
throw new InternalError("the recipt provided by the RPC is invalid");
}
const tx = block.transactions[index];
return {
transactionHash: txHash,
transactionIndex: bigIntToHex(index),
blockHash: bufferToHex(block.hash()),
blockNumber: bigIntToHex(block.header.number),
from: tx.getSenderAddress().toString(),
to: tx.to?.toString() ?? null,
cumulativeGasUsed: "0x0",
effectiveGasPrice: "0x0",
gasUsed: "0x0",
contractAddress: null,
logs: [],
logsBloom: "0x0",
status: BigInt(receipt.status) ? "0x1" : "0x0", // unverified!!
};
}
private async getVMCopy(): Promise<VM> {
if (this.vm === null) {
const blockchain = await Blockchain.create({ common: this.common });
// path the blockchain to return the correct blockhash
(blockchain as any).getBlock = async (blockId: number) => {
const _hash = toBuffer(await this.getBlockHash(BigInt(blockId)));
return {
hash: () => _hash,
};
};
this.vm = await VM.create({ common: this.common, blockchain });
}
return await this.vm!.copy();
}
private async getVM(tx: RPCTx, header: BlockHeader): Promise<VM> {
// forcefully set gasPrice to 0 to avoid not enough balance error
const _tx = {
to: tx.to,
from: tx.from ? tx.from : ZERO_ADDR,
data: tx.data,
value: tx.value,
gasPrice: "0x0",
gas: tx.gas ? tx.gas : bigIntToHex(header.gasLimit!),
};
const { result, success } = await this.rpc.request({
method: "eth_createAccessList",
params: [_tx, bigIntToHex(header.number)],
});
if (!success) {
throw new InternalError(`RPC request failed`);
}
const accessList = result.accessList as AccessList;
accessList.push({ address: _tx.from, storageKeys: [] });
if (_tx.to && !accessList.some((a) => a.address.toLowerCase() === _tx.to)) {
accessList.push({ address: _tx.to, storageKeys: [] });
}
const vm = await this.getVMCopy();
await vm.stateManager.checkpoint();
const requests = accessList
.map((access) => {
return [
{
method: "eth_getProof",
params: [
access.address,
access.storageKeys,
bigIntToHex(header.number),
],
},
{
method: "eth_getCode",
params: [access.address, bigIntToHex(header.number)],
},
];
})
.flat();
const rawResponse = await this.rpc.requestBatch(requests);
if (rawResponse.some((r: any) => !r.success)) {
throw new InternalError(`RPC request failed`);
}
const responses = _.chunk(
rawResponse.map((r: any) => r.result),
2
) as [AccountResponse, CodeResponse][];
for (let i = 0; i < accessList.length; i++) {
const { address: addressHex, storageKeys } = accessList[i];
const [accountProof, code] = responses[i];
const {
nonce,
balance,
codeHash,
storageProof: storageAccesses,
} = accountProof;
const address = Address.fromString(addressHex);
const isAccountCorrect = await this.verifyProof(
address,
storageKeys,
header.stateRoot,
accountProof
);
if (!isAccountCorrect) {
throw new InternalError(`invalid account proof provided by the RPC`);
}
const isCodeCorrect = await this.verifyCodeHash(code, codeHash);
if (!isCodeCorrect) {
throw new InternalError(
`code provided by the RPC doesn't match the account's codeHash`
);
}
const account = Account.fromAccountData({
nonce: BigInt(nonce),
balance: BigInt(balance),
codeHash,
});
await vm.stateManager.putAccount(address, account);
for (let storageAccess of storageAccesses) {
await vm.stateManager.putContractStorage(
address,
setLengthLeft(toBuffer(storageAccess.key), 32),
setLengthLeft(toBuffer(storageAccess.value), 32)
);
}
if (code !== "0x")
await vm.stateManager.putContractCode(address, toBuffer(code));
}
await vm.stateManager.commit();
return vm;
}
private async getBlockHeader(blockOpt: BlockOpt): Promise<BlockHeader> {
const blockNumber = this.getBlockNumberByBlockOpt(blockOpt);
await this.waitForBlockNumber(blockNumber);
const blockHash = await this.getBlockHash(blockNumber);
return this.getBlockHeaderByHash(blockHash);
}
private getBlockNumberByBlockOpt(blockOpt: BlockOpt): bigint {
// TODO: add support for blockOpts below
if (
typeof blockOpt === "string" &&
["pending", "earliest", "finalized", "safe"].includes(blockOpt)
) {
throw new InvalidParamsError(`"pending" is not yet supported`);
} else if (blockOpt === "latest") {
return this.latestBlockNumber;
} else {
const blockNumber = BigInt(blockOpt as any);
if (blockNumber > this.latestBlockNumber + MAX_BLOCK_FUTURE) {
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}`
);
}
return blockNumber;
}
}
private async waitForBlockNumber(blockNumber: bigint) {
if (blockNumber <= this.latestBlockNumber) return;
console.log(`waiting for blockNumber ${blockNumber}`);
const blockNumberHex = bigIntToHex(blockNumber);
if (!(blockNumberHex in this.blockPromises)) {
let r: () => void = () => {};
const p = new Promise<void>((resolve) => {
r = resolve;
});
this.blockPromises[blockNumberHex] = {
promise: p,
resolve: r,
};
}
return this.blockPromises[blockNumberHex].promise;
}
private async getBlockHeaderByHash(blockHash: Bytes32) {
if (!this.blockHeaders[blockHash]) {
const { result: blockInfo, success } = await this.rpc.request({
method: "eth_getBlockByHash",
params: [blockHash, true],
});
if (!success) {
throw new InternalError(`RPC request failed`);
}
const headerData = headerDataFromWeb3Response(blockInfo);
const header = BlockHeader.fromHeaderData(headerData);
if (!header.hash().equals(toBuffer(blockHash))) {
throw new InternalError(
`blockhash doesn't match the blockInfo provided by the RPC`
);
}
this.blockHeaders[blockHash] = header;
}
return this.blockHeaders[blockHash];
}
private async verifyProof(
address: Address,
storageKeys: Bytes32[],
stateRoot: Buffer,
proof: GetProof
): Promise<boolean> {
const trie = new Trie();
const key = keccak256(address.toString());
const expectedAccountRLP = await trie.verifyProof(
stateRoot,
toBuffer(key),
proof.accountProof.map((a) => toBuffer(a))
);
const account = Account.fromAccountData({
nonce: BigInt(proof.nonce),
balance: BigInt(proof.balance),
storageRoot: proof.storageHash,
codeHash: proof.codeHash,
});
const isAccountValid = account
.serialize()
.equals(expectedAccountRLP ? expectedAccountRLP : emptyAccountSerialize);
if (!isAccountValid) return false;
for (let i = 0; i < storageKeys.length; i++) {
const sp = proof.storageProof[i];
const key = keccak256(
bufferToHex(setLengthLeft(toBuffer(storageKeys[i]), 32))
);
const expectedStorageRLP = await trie.verifyProof(
toBuffer(proof.storageHash),
toBuffer(key),
sp.proof.map((a) => toBuffer(a))
);
const isStorageValid =
(!expectedStorageRLP && sp.value === "0x0") ||
(!!expectedStorageRLP &&
expectedStorageRLP.equals(Buffer.from(rlp.encode(sp.value))));
if (!isStorageValid) return false;
}
return true;
}
private verifyCodeHash(code: Bytes, codeHash: Bytes32): boolean {
return (
(code === "0x" && codeHash === "0x" + KECCAK256_NULL_S) ||
keccak256(code) === codeHash
);
}
private validateTx(tx: RPCTx) {
if (tx.gasPrice !== undefined && tx.maxFeePerGas !== undefined) {
throw new Error("Cannot send both gasPrice and maxFeePerGas params");
}
if (tx.gasPrice !== undefined && tx.maxPriorityFeePerGas !== undefined) {
throw new Error("Cannot send both gasPrice and maxPriorityFeePerGas");
}
if (
tx.maxFeePerGas !== undefined &&
tx.maxPriorityFeePerGas !== undefined &&
BigInt(tx.maxPriorityFeePerGas) > BigInt(tx.maxFeePerGas)
) {
throw new Error(
`maxPriorityFeePerGas (${tx.maxPriorityFeePerGas.toString()}) is bigger than maxFeePerGas (${tx.maxFeePerGas.toString()})`
);
}
}
private async getBlock(header: BlockHeader) {
const { result: blockInfo, success } = await this.rpc.request({
method: "eth_getBlockByNumber",
params: [bigIntToHex(header.number), true],
});
if (!success) {
throw new InternalError(`RPC request failed`);
}
// TODO: add support for uncle headers; First fetch all the uncles
// add it to the blockData, verify the uncles and use it
const blockData = blockDataFromWeb3Response(blockInfo);
const block = Block.fromBlockData(blockData, { common: this.common });
if (!block.header.hash().equals(header.hash())) {
throw new InternalError(
`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`
);
}
return block;
}
private async getBlockHash(blockNumber: bigint) {
if (blockNumber > this.latestBlockNumber)
throw new Error("cannot return blockhash for a blocknumber in future");
// TODO: fetch the blockHeader is batched request
let lastVerifiedBlockNumber = this.latestBlockNumber;
while (lastVerifiedBlockNumber > blockNumber) {
const hash = this.blockHashes[bigIntToHex(lastVerifiedBlockNumber)];
const header = await this.getBlockHeaderByHash(hash);
lastVerifiedBlockNumber--;
const parentBlockHash = bufferToHex(header.parentHash);
const parentBlockNumberHex = bigIntToHex(lastVerifiedBlockNumber);
if (
parentBlockNumberHex in this.blockHashes &&
this.blockHashes[parentBlockNumberHex] !== parentBlockHash
) {
console.log(
"Overriding an existing verified blockhash. Possibly the chain had a reorg"
);
}
this.blockHashes[parentBlockNumberHex] = parentBlockHash;
}
return this.blockHashes[bigIntToHex(blockNumber)];
}
}

81
src/client/rpc/rpc.ts Normal file
View File

@ -0,0 +1,81 @@
export type RPCRequest = {
method: string;
params: any[];
};
export type RPCRequestRaw = RPCRequest & {
jsonrpc: string;
id: string;
};
export type RPCResponse = {
success: boolean;
result: any;
};
export class RPC {
private callback: Function;
constructor(callback: Function) {
this.callback = callback;
}
async request(request: RPCRequest) {
return await this._retryRequest(request);
}
async requestBatch(requests: RPCRequest[]) {
const res = [];
for (const request of requests) {
const r = await this._retryRequest(request);
res.push(r);
}
return res;
}
private async _retryRequest(
_request: RPCRequest,
retry = 5
): Promise<RPCResponse> {
const request = {
..._request,
jsonrpc: "2.0",
id: this.generateId(),
};
for (let i = retry; i > 0; i--) {
const res = await this._request(request);
if (res.success) {
return res;
} else if (i == 1) {
console.error(
`RPC batch request failed after maximum retries: ${JSON.stringify(
request,
null,
2
)} ${JSON.stringify(res, null, 2)}`
);
}
}
throw new Error("RPC request failed");
}
private generateId(): string {
return Math.floor(Math.random() * 2 ** 64).toFixed();
}
protected async _request(request: RPCRequestRaw): Promise<RPCResponse> {
try {
const response = await this.callback(request);
return {
success: !response.error,
result: response.error || response.result,
};
} catch (e) {
return {
success: false,
result: { message: `request failed: ${e}` },
};
}
}
}

63
src/client/rpc/types.ts Normal file
View File

@ -0,0 +1,63 @@
import type { GetProof } from "web3-eth";
import type { BlockNumber } from "web3-core";
import type { Method } from "web3-core-method";
import type { JsonTx } from "@ethereumjs/tx";
export type { GetProof, BlockNumber, Method };
export type Bytes = string;
export type Bytes32 = string;
export type AddressHex = string;
export type ChainId = number;
export type HexString = string;
// Some of the types below are taken from:
// https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/client/lib/rpc/modules/eth.ts
export type AccessList = { address: AddressHex; storageKeys: Bytes32[] }[];
export interface RPCTx {
from?: string;
to?: string;
gas?: string;
gasPrice?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
accessList?: AccessList;
value?: string;
data?: string;
}
export type AccountResponse = GetProof;
export type CodeResponse = string;
export type JSONRPCReceipt = {
transactionHash: string; // DATA, 32 Bytes - hash of the transaction.
transactionIndex: string; // QUANTITY - integer of the transactions index position in the block.
blockHash: string; // DATA, 32 Bytes - hash of the block where this transaction was in.
blockNumber: string; // QUANTITY - block number where this transaction was in.
from: string; // DATA, 20 Bytes - address of the sender.
to: string | null; // DATA, 20 Bytes - address of the receiver. null when it's a contract creation transaction.
cumulativeGasUsed: string; // QUANTITY - The total amount of gas used when this transaction was executed in the block.
effectiveGasPrice: string; // QUANTITY - The final gas price per gas paid by the sender in wei.
gasUsed: string; // QUANTITY - The amount of gas used by this specific transaction alone.
contractAddress: string | null; // DATA, 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null.
logs: JSONRPCLog[]; // Array - Array of log objects, which this transaction generated.
logsBloom: string; // DATA, 256 Bytes - Bloom filter for light clients to quickly retrieve related logs.
// It also returns either:
root?: string; // DATA, 32 bytes of post-transaction stateroot (pre Byzantium)
status?: string; // QUANTITY, either 1 (success) or 0 (failure)
};
export type JSONRPCLog = {
removed: boolean; // TAG - true when the log was removed, due to a chain reorganization. false if it's a valid log.
logIndex: string | null; // QUANTITY - integer of the log index position in the block. null when it's pending.
transactionIndex: string | null; // QUANTITY - integer of the transactions index position log was created from. null when it's pending.
transactionHash: string | null; // DATA, 32 Bytes - hash of the transactions this log was created from. null when it's pending.
blockHash: string | null; // DATA, 32 Bytes - hash of the block where this log was in. null when it's pending.
blockNumber: string | null; // QUANTITY - the block number where this log was in. null when it's pending.
address: string; // DATA, 20 Bytes - address from which this log originated.
data: string; // DATA - contains one or more 32 Bytes non-indexed arguments of the log.
topics: string[]; // Array of DATA - Array of 0 to 4 32 Bytes DATA of indexed log arguments.
// (In solidity: The first topic is the hash of the signature of the event
// (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)
};

68
src/client/rpc/utils.ts Normal file
View File

@ -0,0 +1,68 @@
import {
setLengthLeft,
toBuffer,
bigIntToHex,
bufferToHex,
intToHex,
} from "@ethereumjs/util";
import { HeaderData, BlockData, Block } from "@ethereumjs/block";
import {
TxData,
AccessListEIP2930TxData,
FeeMarketEIP1559TxData,
TypedTransaction,
} from "@ethereumjs/tx";
const isTruthy = (val: any) => !!val;
// TODO: fix blockInfo type
export function headerDataFromWeb3Response(blockInfo: any): HeaderData {
return {
parentHash: blockInfo.parentHash,
uncleHash: blockInfo.sha3Uncles,
coinbase: blockInfo.miner,
stateRoot: blockInfo.stateRoot,
transactionsTrie: blockInfo.transactionsRoot,
receiptTrie: blockInfo.receiptsRoot,
logsBloom: blockInfo.logsBloom,
difficulty: BigInt(blockInfo.difficulty),
number: BigInt(blockInfo.number),
gasLimit: BigInt(blockInfo.gasLimit),
gasUsed: BigInt(blockInfo.gasUsed),
timestamp: BigInt(blockInfo.timestamp),
extraData: blockInfo.extraData,
mixHash: (blockInfo as any).mixHash, // some reason the types are not up to date :(
nonce: blockInfo.nonce,
baseFeePerGas: blockInfo.baseFeePerGas
? BigInt(blockInfo.baseFeePerGas)
: undefined,
};
}
export function txDataFromWeb3Response(
txInfo: any
): TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData {
return {
...txInfo,
data: txInfo.input,
gasPrice: BigInt(txInfo.gasPrice),
gasLimit: txInfo.gas,
to: isTruthy(txInfo.to)
? setLengthLeft(toBuffer(txInfo.to), 20)
: undefined,
value: BigInt(txInfo.value),
maxFeePerGas: isTruthy(txInfo.maxFeePerGas)
? BigInt(txInfo.maxFeePerGas)
: undefined,
maxPriorityFeePerGas: isTruthy(txInfo.maxPriorityFeePerGas)
? BigInt(txInfo.maxPriorityFeePerGas)
: undefined,
};
}
export function blockDataFromWeb3Response(blockInfo: any): BlockData {
return {
header: headerDataFromWeb3Response(blockInfo),
transactions: blockInfo.transactions.map(txDataFromWeb3Response),
};
}

View File

@ -0,0 +1,139 @@
import { InvalidParamsError } from './errors';
// Most of the validations are taken from:
// https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/client/lib/rpc/validation.ts
/**
* @memberof module:rpc
*/
export const validators = {
/**
* address validator to ensure has `0x` prefix and 20 bytes length
* @param params parameters of method
* @param index index of parameter
*/
address(params: any[], index: number) {
this.hex(params, index);
const address = params[index].substr(2);
if (!/^[0-9a-fA-F]+$/.test(address) || address.length !== 40) {
throw new InvalidParamsError(
`invalid argument ${index}: invalid address`,
);
}
},
/**
* hex validator to ensure has `0x` prefix
* @param params parameters of method
* @param index index of parameter
*/
hex(params: any[], index: number) {
if (typeof params[index] !== 'string') {
throw new InvalidParamsError(
`invalid argument ${index}: argument must be a hex string`,
);
}
if (params[index].substr(0, 2) !== '0x') {
throw new InvalidParamsError(
`invalid argument ${index}: hex string without 0x prefix`,
);
}
},
/**
* hex validator to validate block hash
* @param params parameters of method
* @param index index of parameter
*/
blockHash(params: any[], index: number) {
this.hex(params, index);
const blockHash = params[index].substring(2);
if (!/^[0-9a-fA-F]+$/.test(blockHash) || blockHash.length !== 64) {
throw new InvalidParamsError(
`invalid argument ${index}: invalid block hash`,
);
}
},
/**
* validator to ensure valid block integer or hash, or string option ["latest", "earliest", "pending"]
* @param params parameters of method
* @param index index of parameter
*/
blockOption(params: any[], index: number) {
const blockOption = params[index];
if (typeof blockOption !== 'string') {
throw new InvalidParamsError(
`invalid argument ${index}: argument must be a string`,
);
}
try {
if (['latest', 'earliest', 'pending'].includes(blockOption)) {
return;
}
return this.hex([blockOption], 0);
} catch (e) {
throw new InvalidParamsError(
`invalid argument ${index}: block option must be a valid hex block number, or "latest", "earliest" or "pending"`,
);
}
},
/**
* bool validator to check if type is boolean
* @param params parameters of method
* @param index index of parameter
*/
bool(params: any[], index: number) {
if (typeof params[index] !== 'boolean') {
throw new InvalidParamsError(
`invalid argument ${index}: argument is not boolean`,
);
}
},
/**
* params length validator
* @param params parameters of method
* @requiredParamsLength required length of parameters
*/
paramsLength(
params: any[],
requiredParamsCount: number,
maxParamsCount: number = requiredParamsCount,
) {
if (params.length < requiredParamsCount || params.length > maxParamsCount) {
throw new InvalidParamsError(
`missing value for required argument ${params.length}`,
);
}
},
transaction(params: any[], index: number) {
const tx = params[index];
if (typeof tx !== 'object') {
throw new InvalidParamsError(
`invalid argument ${index}: argument must be an object`,
);
}
// validate addresses
for (const field of [tx.to, tx.from]) {
// TODO: the below will create an error with incorrect index if the tx is not at index 0
if (field !== undefined) this.address([field], 0);
}
// validate hex
for (const field of [tx.gas, tx.gasPrice, tx.value, tx.data]) {
// TODO: the below will create an error with incorrect index if the tx is not at index 0
if (field !== undefined) this.hex([field], 0);
}
},
};

26
src/client/ssz.ts Normal file
View File

@ -0,0 +1,26 @@
import {
ContainerType,
VectorCompositeType,
ByteVectorType,
BooleanType,
UintNumberType,
ListCompositeType,
} from '@chainsafe/ssz';
import * as altair from '@lodestar/types/altair';
import { BEACON_SYNC_COMMITTEE_SIZE } from './constants.js';
const MAX_BATCHSIZE = 10000;
export const LightClientUpdateSSZ = altair.ssz.LightClientUpdate;
export const LightClientUpdatesSSZ = new ListCompositeType(
LightClientUpdateSSZ as any,
MAX_BATCHSIZE,
);
export const CommitteeSSZ = new VectorCompositeType(
new ByteVectorType(48),
BEACON_SYNC_COMMITTEE_SIZE,
);
const HashSSZ = new ByteVectorType(32);
export const HashesSSZ = new ListCompositeType(HashSSZ, MAX_BATCHSIZE);

32
src/client/types.ts Normal file
View File

@ -0,0 +1,32 @@
import { routes } from "@lodestar/api";
import { BeaconConfig } from "@lodestar/config";
import * as altair from "@lodestar/types/altair";
export type PubKeyString = string;
export type Slot = number;
export type Bytes32 = string;
export type OptimisticUpdate = altair.LightClientOptimisticUpdate;
export type LightClientUpdate = altair.LightClientUpdate;
export type GenesisData = {
committee: PubKeyString[];
slot: Slot;
time: number;
};
export type ClientConfig = {
genesis: GenesisData;
chainConfig: BeaconConfig;
// treeDegree in case of Superlight and batchSize in case of Light and Optimistic
n?: number;
};
export type ExecutionInfo = {
blockhash: string;
blockNumber: bigint;
};
export type VerifyWithReason =
| { correct: true }
| { correct: false; reason: string };

112
src/client/utils.ts Normal file
View File

@ -0,0 +1,112 @@
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);
let offset = 0;
data.forEach((d) => {
result.set(d, offset);
offset += d.length;
});
return result;
}
export function isUint8ArrayEq(a: Uint8Array, b: Uint8Array): boolean {
return toHexString(a) === toHexString(b);
}
export function isCommitteeSame(a: Uint8Array[], b: Uint8Array[]): boolean {
if (a.length !== b.length) return false;
return a.every((c, i) => isUint8ArrayEq(c, b[i]));
}
export function generateRandomSyncCommittee(): Uint8Array[] {
let res = [];
// TODO: change 512 to constant
for (let i = 0; i < 512; i++) {
res.push(bls.SecretKey.fromKeygen().toPublicKey().toBytes());
}
return res;
}
export function getRandomInt(max: number) {
return Math.floor(Math.random() * max);
}
export const smallHexStr = (data: Uint8Array) => toHexString(data).slice(0, 8);
export function numberToUint8Array(num: number): Uint8Array {
const rawHex = num.toString(16);
const hex = "0x" + (rawHex.length % 2 === 0 ? rawHex : "0" + rawHex);
return fromHexString(hex);
}
// credit: https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
export function shuffle(array: any[]) {
let currentIndex = array.length,
randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
export async function wait(ms: number) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
/*
export function deepTypecast<T>(
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)
);
return {
genesis: {
committee: mainnetConfig.committee_pk,
slot: parseInt(mainnetConfig.slot),
time: parseInt(mainnetConfig.genesis_time),
},
chainConfig,
n: 1,
};
}

View File

@ -1,14 +1,8 @@
import { ActiveQuery, addHandler, handleMessage } from "libkmodule";
import { createClient, RpcNetwork } from "@lumeweb/kernel-rpc-client";
import init, { Client } from "../wasm/helios_ts.js";
// @ts-ignore
import wasm from "../wasm/helios_ts_bg.wasm?base64";
import { Buffer } from "buffer";
import { RPCResponse } from "@lumeweb/interface-relay";
import { ConsensusRequest, ExecutionRequest } from "./types.js";
const CHECKPOINT =
"0x694433ba78dd08280df68d3713c0f79d668dbee9e0922ec2346fcceb1dc3daa9";
import Client from "./client/client.js";
import { Prover } from "./client/index.js";
onmessage = handleMessage;
@ -48,7 +42,12 @@ addHandler("ready", handleReady);
params: aq.callerInput || {},
method: rpcMethod,
};
aq.respond(await handleRpcMethod(aq));
try {
const ret = await handleRpcMethod(aq);
aq.respond(ret);
} catch (e: any) {
aq.reject((e as Error).message);
}
});
});
@ -59,156 +58,66 @@ async function handlePresentSeed() {
async function handleRpcMethod(aq: ActiveQuery) {
await moduleReady;
switch (aq.callerInput?.method) {
case "eth_accounts":
case "eth_requestAccounts": {
return [];
}
case "eth_getBalance": {
return client.get_balance(
aq.callerInput?.params[0],
aq.callerInput?.params[1]
);
}
case "eth_chainId": {
return client.chain_id();
}
case "eth_blockNumber": {
return client.get_block_number();
}
case "eth_getTransactionByHash": {
let tx = await client.get_transaction_by_hash(aq.callerInput?.params[0]);
return mapToObj(tx);
}
case "eth_getTransactionCount": {
return client.get_transaction_count(
aq.callerInput?.params[0],
aq.callerInput?.params[1]
);
}
case "eth_getBlockTransactionCountByHash": {
return client.get_block_transaction_count_by_hash(
aq.callerInput?.params[0]
);
}
case "eth_getBlockTransactionCountByNumber": {
return client.get_block_transaction_count_by_number(
aq.callerInput?.params[0]
);
}
case "eth_getCode": {
return client.get_code(
aq.callerInput?.params[0],
aq.callerInput?.params[1]
);
}
case "eth_call": {
return client.call(aq.callerInput?.params[0], aq.callerInput?.params[1]);
}
case "eth_estimateGas": {
return client.estimate_gas(aq.callerInput?.params[0]);
}
case "eth_gasPrice": {
return client.gas_price();
}
case "eth_maxPriorityFeePerGas": {
return client.max_priority_fee_per_gas();
}
case "eth_sendRawTransaction": {
return client.send_raw_transaction(aq.callerInput?.params[0]);
}
case "eth_getTransactionReceipt": {
return client.get_transaction_receipt(aq.callerInput?.params[0]);
}
case "eth_getLogs": {
return client.get_logs(aq.callerInput?.params[0]);
}
case "net_version": {
return client.chain_id();
return client.provider.rpcMethod(
aq.callerInput?.method,
// @ts-ignore
aq.callerInput?.params as any[]
);
}
async function consensusHandler(endpoint: string) {
let query;
while (true) {
query = await rpc.simpleQuery({
query: {
module: "eth",
method: "consensus_request",
data: {
method: "GET",
path: endpoint,
} as ConsensusRequest,
},
options: {
relayTimeout: 10,
queryTimeout: 10,
},
});
console.log("consensusHandler", endpoint);
const ret = await query.result;
if (ret.data) {
return ret.data;
}
}
}
async function setup() {
rpc = createClient();
async function executionHandler(data: Map<string, string | any>) {
let query = await rpc.simpleQuery({
query: {
module: "eth",
method: "execution_request",
data,
},
});
console.log("executionHandler", data);
let ret = await query.result;
return ret.data;
}
async function setup() {
console.time("setup");
rpc = createClient();
// @ts-ignore
await (
await rpc.ready
)();
await init(URL.createObjectURL(new Blob([Buffer.from(wasm, "base64")])));
(self as any).consensus_rpc_handler = async (
data: Map<string, string | any>
) => {
const method = data.get("method");
const path = data.get("path");
let query;
let ret: RPCResponse;
while (true) {
query = await rpc.simpleQuery({
query: {
module: "eth",
method: "consensus_request",
data: {
method,
path,
} as ConsensusRequest,
},
options: {
relayTimeout: 10,
queryTimeout: 10,
},
});
ret = await query.result;
if (ret?.data) {
break;
}
}
if (path.startsWith("/eth/v1/beacon/light_client/updates")) {
return JSON.stringify(ret.data);
}
return JSON.stringify({ data: ret.data });
};
(self as any).execution_rpc_handler = async (
data: Map<string, string | any>
) => {
const method = data.get("method");
let params = data.get("params");
params = JSON.parse(params);
let query;
let ret: RPCResponse;
while (true) {
query = await rpc.simpleQuery({
query: {
module: "eth",
method: "execution_request",
data: {
method,
params,
} as ExecutionRequest,
},
});
ret = await query.result;
if (ret?.data) {
break;
}
}
return JSON.stringify(ret.data);
};
client = new Client(CHECKPOINT);
const prover = new Prover(consensusHandler);
client = new Client(prover, executionHandler);
await client.sync();
}
@ -217,12 +126,3 @@ async function handleReady(aq: ActiveQuery) {
aq.respond();
}
function mapToObj(map: Map<any, any> | undefined): Object | undefined {
if (!map) return undefined;
return Array.from(map).reduce((obj: any, [key, value]) => {
obj[key] = value;
return obj;
}, {});
}