refactor: move to new @lumeweb/libethsync library and cleanup plugin
This commit is contained in:
parent
2f5c27ce5d
commit
0d88db7cb5
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
|
@ -3,36 +3,9 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chainsafe/as-sha256": "^0.3.1",
|
|
||||||
"@chainsafe/bls": "^7.1.1",
|
|
||||||
"@chainsafe/blst": "0.2.9",
|
|
||||||
"@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/config": "^1.7.0",
|
|
||||||
"@lodestar/light-client": "^1.7.0",
|
|
||||||
"@lodestar/params": "^1.8.0",
|
|
||||||
"@lodestar/types": "^1.7.0",
|
|
||||||
"@lumeweb/presetter-relay-plugin-preset": "^0.1.0-develop.8",
|
"@lumeweb/presetter-relay-plugin-preset": "^0.1.0-develop.8",
|
||||||
"async-mutex": "^0.4.0",
|
|
||||||
"ethers": "^6.2.3",
|
|
||||||
"json-rpc-2.0": "^1.5.1",
|
|
||||||
"json-stringify-deterministic": "^1.0.8",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"node-cache": "^5.1.2",
|
|
||||||
"node-fetch": "^3.3.1",
|
|
||||||
"patch-package": "^7.0.0",
|
"patch-package": "^7.0.0",
|
||||||
"presetter": "^4.0.1",
|
"presetter": "^4.0.1"
|
||||||
"rlp": "^3.0.0",
|
|
||||||
"ts-essentials": "^9.3.1",
|
|
||||||
"web3-core": "^1.10.0",
|
|
||||||
"web3-core-method": "^1.10.0",
|
|
||||||
"web3-eth": "^1.10.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "presetter bootstrap",
|
"prepare": "presetter bootstrap",
|
||||||
|
@ -41,6 +14,8 @@
|
||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lumeweb/interface-relay": "^0.0.2-develop.1"
|
"@lumeweb/interface-relay": "^0.0.2-develop.1",
|
||||||
|
"@lumeweb/libethsync": "^0.1.0-develop.16",
|
||||||
|
"axios": "^1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,397 +0,0 @@
|
||||||
import {
|
|
||||||
Bytes32,
|
|
||||||
ClientConfig,
|
|
||||||
ExecutionInfo,
|
|
||||||
LightClientUpdate,
|
|
||||||
OptimisticUpdate,
|
|
||||||
VerifyWithReason,
|
|
||||||
} from "./types.js";
|
|
||||||
import { getDefaultClientConfig, handleGETRequest } from "./utils.js";
|
|
||||||
import { IProver, IStore } from "./interfaces.js";
|
|
||||||
import {
|
|
||||||
BEACON_SYNC_SUPER_MAJORITY,
|
|
||||||
DEFAULT_BATCH_SIZE,
|
|
||||||
POLLING_DELAY,
|
|
||||||
} from "./constants.js";
|
|
||||||
import {
|
|
||||||
computeSyncPeriodAtSlot,
|
|
||||||
getCurrentSlot,
|
|
||||||
isValidMerkleBranch,
|
|
||||||
} from "@lodestar/light-client/utils";
|
|
||||||
import {
|
|
||||||
assertValidLightClientUpdate,
|
|
||||||
assertValidSignedHeader,
|
|
||||||
} from "@lodestar/light-client/validation";
|
|
||||||
import { SyncCommitteeFast } from "@lodestar/light-client";
|
|
||||||
import bls, { init } from "@chainsafe/bls/switchable";
|
|
||||||
// @ts-ignore
|
|
||||||
import type { PublicKey } from "@chainsafe/bls/types.js";
|
|
||||||
import { fromHexString, toHexString } from "@chainsafe/ssz";
|
|
||||||
import * as phase0 from "@lodestar/types/phase0";
|
|
||||||
import * as capella from "@lodestar/types/capella";
|
|
||||||
import NodeCache from "node-cache";
|
|
||||||
import { Mutex } from "async-mutex";
|
|
||||||
import { VerifyingProvider } from "./rpc/index.js";
|
|
||||||
import { ChainForkConfig } from "@lodestar/config";
|
|
||||||
import { allForks } from "@lodestar/types";
|
|
||||||
import {
|
|
||||||
BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH,
|
|
||||||
BLOCK_BODY_EXECUTION_PAYLOAD_INDEX as EXECUTION_PAYLOAD_INDEX,
|
|
||||||
} from "@lodestar/params";
|
|
||||||
|
|
||||||
export default class Client {
|
|
||||||
latestCommittee?: Uint8Array[];
|
|
||||||
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 boot = false;
|
|
||||||
private beaconChainAPIURL: string;
|
|
||||||
private store: IStore;
|
|
||||||
private syncMutex = new Mutex();
|
|
||||||
private rpcUrl: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
prover: IProver,
|
|
||||||
store: IStore,
|
|
||||||
beaconUrl: string,
|
|
||||||
rpcUrl: string,
|
|
||||||
) {
|
|
||||||
this.prover = prover;
|
|
||||||
this.store = store;
|
|
||||||
this.beaconChainAPIURL = beaconUrl;
|
|
||||||
this.rpcUrl = rpcUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _provider?: VerifyingProvider;
|
|
||||||
|
|
||||||
get provider(): VerifyingProvider {
|
|
||||||
return this._provider as VerifyingProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _latestPeriod: number = -1;
|
|
||||||
|
|
||||||
get latestPeriod(): number {
|
|
||||||
return this._latestPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _blockCache = new NodeCache({ stdTTL: 60 * 60 * 12 });
|
|
||||||
|
|
||||||
get blockCache(): NodeCache {
|
|
||||||
return this._blockCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _blockHashCache = new NodeCache();
|
|
||||||
|
|
||||||
get blockHashCache(): NodeCache {
|
|
||||||
return this._blockHashCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get isSynced() {
|
|
||||||
return this._latestPeriod === this.getCurrentPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async sync(): Promise<void> {
|
|
||||||
await init("herumi");
|
|
||||||
|
|
||||||
await this._sync();
|
|
||||||
|
|
||||||
if (!this._provider) {
|
|
||||||
const { blockhash, blockNumber } = await this.getNextValidExecutionInfo();
|
|
||||||
const provider = new VerifyingProvider(
|
|
||||||
this.rpcUrl,
|
|
||||||
blockNumber,
|
|
||||||
blockhash,
|
|
||||||
);
|
|
||||||
this._provider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ei = await this.getLatestExecution();
|
|
||||||
if (ei && ei.blockhash !== this.latestBlockHash) {
|
|
||||||
this.latestBlockHash = ei.blockhash;
|
|
||||||
}
|
|
||||||
this._provider.update(ei?.blockhash as string, ei?.blockNumber as bigint);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.store.addUpdate(period, update);
|
|
||||||
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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
optimisticUpdateFromJSON(update: any): OptimisticUpdate {
|
|
||||||
return capella.ssz.LightClientOptimisticUpdate.fromJson(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
async optimisticUpdateVerify(
|
|
||||||
committee: Uint8Array[],
|
|
||||||
update: OptimisticUpdate,
|
|
||||||
): Promise<VerifyWithReason> {
|
|
||||||
try {
|
|
||||||
const { attestedHeader: header, syncAggregate } = update;
|
|
||||||
const headerBlockRoot = phase0.ssz.BeaconBlockHeader.hashTreeRoot(
|
|
||||||
header.beacon,
|
|
||||||
);
|
|
||||||
const committeeFast = this.deserializeSyncCommittee(committee);
|
|
||||||
try {
|
|
||||||
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" };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isValidLightClientHeader(this.config.chainConfig, header)) {
|
|
||||||
return { correct: false, reason: "invalid header" };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { correct: true };
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return { correct: false, reason: (e as Error).message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isValidLightClientHeader(
|
|
||||||
config: ChainForkConfig,
|
|
||||||
header: allForks.LightClientHeader,
|
|
||||||
): boolean {
|
|
||||||
return isValidMerkleBranch(
|
|
||||||
config
|
|
||||||
.getExecutionForkTypes(header.beacon.slot)
|
|
||||||
.ExecutionPayloadHeader.hashTreeRoot(
|
|
||||||
(header as capella.LightClientHeader).execution,
|
|
||||||
),
|
|
||||||
(header as capella.LightClientHeader).executionBranch,
|
|
||||||
EXECUTION_PAYLOAD_DEPTH,
|
|
||||||
EXECUTION_PAYLOAD_INDEX,
|
|
||||||
header.beacon.bodyRoot,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getExecutionFromBlockRoot(
|
|
||||||
slot: bigint,
|
|
||||||
expectedBlockRoot: Bytes32,
|
|
||||||
): Promise<ExecutionInfo> {
|
|
||||||
const res = await handleGETRequest(
|
|
||||||
`${this.beaconChainAPIURL}/eth/v2/beacon/blocks/${slot}`,
|
|
||||||
);
|
|
||||||
if (!res) {
|
|
||||||
throw Error(`fetching block failed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const blockJSON = res.data.message.body;
|
|
||||||
const block = capella.ssz.BeaconBlockBody.fromJson(blockJSON);
|
|
||||||
const blockRoot = toHexString(
|
|
||||||
capella.ssz.BeaconBlockBody.hashTreeRoot(block),
|
|
||||||
);
|
|
||||||
if (blockRoot !== expectedBlockRoot) {
|
|
||||||
throw Error(
|
|
||||||
`block provided by the beacon chain api doesn't match the expected block root`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._blockCache.set(slot as any, res);
|
|
||||||
this._blockHashCache.set(slot as any, expectedBlockRoot);
|
|
||||||
|
|
||||||
return {
|
|
||||||
blockhash: blockJSON.execution_payload.block_hash,
|
|
||||||
blockNumber: blockJSON.execution_payload.block_number,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _sync() {
|
|
||||||
await this.syncMutex.acquire();
|
|
||||||
|
|
||||||
const currentPeriod = this.getCurrentPeriod();
|
|
||||||
if (currentPeriod > this._latestPeriod) {
|
|
||||||
if (!this.boot) {
|
|
||||||
this.latestCommittee = await this.syncFromGenesis();
|
|
||||||
} else {
|
|
||||||
this.latestCommittee = await this.syncFromLastUpdate();
|
|
||||||
}
|
|
||||||
this._latestPeriod = currentPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.syncMutex.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async syncFromLastUpdate() {
|
|
||||||
// get the tree size by currentPeriod - genesisPeriod
|
|
||||||
const currentPeriod = this.getCurrentPeriod();
|
|
||||||
let startPeriod = this.latestPeriod;
|
|
||||||
|
|
||||||
let startCommittee = this.latestCommittee;
|
|
||||||
|
|
||||||
const { syncCommittee, period } = await this.syncProver(
|
|
||||||
startPeriod,
|
|
||||||
currentPeriod,
|
|
||||||
startCommittee as Uint8Array[],
|
|
||||||
);
|
|
||||||
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 handleGETRequest(
|
|
||||||
`${this.beaconChainAPIURL}/eth/v1/beacon/light_client/optimistic_update`,
|
|
||||||
);
|
|
||||||
if (!updateJSON) {
|
|
||||||
throw Error(`fetching optimistic update failed`);
|
|
||||||
}
|
|
||||||
const update = this.optimisticUpdateFromJSON(updateJSON.data);
|
|
||||||
const verify = await this.optimisticUpdateVerify(
|
|
||||||
this.latestCommittee as Uint8Array[],
|
|
||||||
update,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!verify.correct) {
|
|
||||||
// @ts-ignore
|
|
||||||
console.error(`Invalid Optimistic Update: ${verify?.reason}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.getExecutionFromBlockRoot(
|
|
||||||
updateJSON.data.attested_header.beacon.slot,
|
|
||||||
updateJSON.data.attested_header.beacon.body_root,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,532 +0,0 @@
|
||||||
export const BEACON_SYNC_COMMITTEE_SIZE = 512;
|
|
||||||
|
|
||||||
export const mainnetConfig = {
|
|
||||||
genesis_time: "1606824023",
|
|
||||||
genesis_validator_root:
|
|
||||||
"0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95",
|
|
||||||
slot: "6275237",
|
|
||||||
committee_pk: [
|
|
||||||
"0x9163821b21398fabd7fc110483c7100d4561f84d34eb8fe702762f5ad15b21c1de05b7552e400750ef87107054d2024c",
|
|
||||||
"0xb5b3eed089c61c731ea4b8da89d79953d1dcd076f0388e5a9a83d06601ccf8daf0b853ca706f79fee60ed2cd9a385f24",
|
|
||||||
"0xa84a73bbc11ea45136d6b315ae470c6494c418f9ec66648f3345f73f886e2b79171f1dc8bd7ca678064a71976ddd6866",
|
|
||||||
"0x86de1398c34f7ef2a0d6ad10f8d09591c7d1fa1dc5336513bba619933c0bd48be956367261aad5d17553aa6052434b34",
|
|
||||||
"0xb97cb89ec7055b04c7b9fd7eccc4a45715395c7316e42c9e241eadaaf7744b82b70b78acd6c64f95dd7138978356cd6c",
|
|
||||||
"0x8ee3b246427b9a208b272db2613fdde603b40550e0f3205a6f005d5e38cb028bdba44803287a78bc9c577324ae9e5251",
|
|
||||||
"0xa29f0427fc761318e6122277937b066950a6d749a5795f26e80b9fe1d2b41accb26c344b241d39e354638892b7f617f4",
|
|
||||||
"0x91246b7f68034b436ba623979c9a665746f3de5de495115c042a2332f23578131ed0c61aa69c24a1b1c3ea0732dec40b",
|
|
||||||
"0x8dbe5cf5acd2e860969a29165e04c5e7126070526dc160e2a77ebdccb1921be81bc1cc8d6d3bf704ff5efa9e47e4c355",
|
|
||||||
"0xa372476af4b0896ffb47efe4725537f9d4a49e563058008c74d1397f5d0a66c934b2c29078bd8e89f4b055d817453f03",
|
|
||||||
"0x8deaccd6915a70536cf44352e36139ce41dcac10bdf28616cb61b0b3f2dfb819267255709969497c986602d46f24e49e",
|
|
||||||
"0x877c5114d0fd6b1e3fe5853d297c31768c25f5aaf4a56d21c0d1167497009c9360e323810fefa294768555509f1da41f",
|
|
||||||
"0xa909c87e08c44584aae53f9380652edbf2373a0f7042e57d4340b4353d932495dadc8f340048180e578d9f93be77ca74",
|
|
||||||
"0x84e194104f1fab523bb2e5a91367612b15bdcd4d736e7f4633df695e0c1aabedea01b3440915cf0790661eb2a47af9f4",
|
|
||||||
"0xb1b58b4efd14539178aa289ca9a1a3b57efc8b12e3fb94ce6ee0014d7ad30c5c93f4e652cb5773f9f7d375f3e01dce3b",
|
|
||||||
"0x92b2a5471d662a302c6b2b90f3eae2c055ac3eeaf29c90f1a41f43eb4564f0eb15a641077f83bf4b59b61dec0bd651d5",
|
|
||||||
"0xb83ad1c495bc838742bebf2b5a31f4abf201ec93565146b95417a6794ad9c9cb0b944d7945d35735995b248d0d30f687",
|
|
||||||
"0xb64c8327897cb8b16a02a180242ea8eb3a7e8053db0ee4bbdc83bbf00b87b2300f386e43caf4dfac7627844a441d1727",
|
|
||||||
"0x836eba6f2f08438b95cc2f9e8f45ec42fc843892def3005df9291de9ff0bb8a26442270c6acbad1ea009b1026dfdd09b",
|
|
||||||
"0x90fcf0aecdf3bae07054961cd0b13922a61215e2bc128d481cbaa76eb98dae426b5fc848d4c357b46aff6eabf766ba6b",
|
|
||||||
"0x8208bfbf689752bed2c110b19f66d90e336be2f93331f0e32f355652a258366f79843a0f528f17c893e6a7feaadc5a11",
|
|
||||||
"0xa1e099011217619de38f780b46ab4bb97c88968ba994e3e0b47c9c32fabc985f1f74fb40deafa6670d5fcb41add29c4d",
|
|
||||||
"0x8184b64e342d9c02e47461b494782247bb178613d6ba814b7e96dab1170675fb45d0a2dddd3cf119361c022ffb58f55e",
|
|
||||||
"0x939fddb7877e6064ad39888f06ee733bbfb8523adc72d381e5429592ecbfb16fd26cc7b009fcfa03c56390f2e5da7cda",
|
|
||||||
"0x98421b84b58fbf62c1017c0b252848ae09d91c461693834e8b016a3dc476fa95aa4673fe858422a1ddacc0f7c85ef4c5",
|
|
||||||
"0xa3f69eb68d11d8fdf2f3718b114a8f6deda97474e9986e879acd503c0cb4cb2de319a015ce28c10f161f4fa485354857",
|
|
||||||
"0x8c14681476836e1b86f9d6c52c56b48269d1ef359f6006eac5063b7831cc73ab5d30eaba8a1fa99edbe9da03e9755969",
|
|
||||||
"0xa4f1ecba907961d6a86aea821550b28f00551814188b0f5ddbbb486f3ac4d063554c48b4cd685b7104707f1b13dc1b2a",
|
|
||||||
"0x96d7b0775111dbbb747242bbf9735e441aa9775a58f915418dff8507a0990532256bf0dd03b8bab011d99ff6e2c35cd9",
|
|
||||||
"0x8c81dcaa669a0ce8fa46920385ed3abdcc8b8a3209a176c2d2aff1f3a1deee8f22e2b9477a33d58c1d91ec62eda1a931",
|
|
||||||
"0xae05610abffb2d21709f64b04d4b59e12a0d8a738fb8e9161705b0bbbf40a69a7f432850c82c4bfff96db7875fb69ced",
|
|
||||||
"0x8078a2aea4c2b52d93f2686caa0f128b18600c02b24725314b2d02e701ba0b60c8d1f168de8e4364b2cae31ecced4094",
|
|
||||||
"0xb6a4f4de40f1c39a3d99dda56fa3815d394e4585632e98c39873348053006099c317db09b2028e9d45644dbeab45a9fb",
|
|
||||||
"0x85950570508f1567455863018614ee8aaf58aff2e6d5f54c7ec3b8cad69257bc642355fbb652eea772b4e8f5ea6dfcb5",
|
|
||||||
"0xa8f5a8bf2ea05042da3a5e77e881e4002dceee5229839c2e19a78931a91fd162a8ca1b70493d21bb8c80dbc0d03ac10f",
|
|
||||||
"0x8e6e04cd6608d961a225c22b9d07420f545dba1855de35a2e541b15843c2fb9abc4165c61bca5f5ab8298870d99054ef",
|
|
||||||
"0x992de481659c968b23c03dcdd5a8ad0fa9d00ad54e6a348f754b6dfda5e133bea2c52c295059a6467e4d1e65636f8d0f",
|
|
||||||
"0x95c16a1ba548e4d7af503921a92d4af407e6da7cc383ce5153916b2dfb49ccd27fd513a1a0599b8b0a2bf75c0c3dff10",
|
|
||||||
"0xab7bf86088748ebd4a24eb8db91bc72351094e0203f3109194ff1506e239bf9102d128a37ecd911005e74ab7ee57397b",
|
|
||||||
"0xb375c65fddd939dd19563262fe63cda6f8eef858cce226c95023acff69a3748fdc15d6e2b51caeb6adc60e36cb741ef6",
|
|
||||||
"0x8cacf8dfc5ca961a13ab4adc6a21e2c8d81a575d70eb2007bf6d78d8e01b032b2774e978855d226d8fceb2c57b5cd69a",
|
|
||||||
"0x9279d1e437728bbb0c9f79b534a5c394e08c48b5f39fdbc5f4743a36f5b58717bcd0def9c30170f4e2ce761137645f14",
|
|
||||||
"0xb2c5e1fb714cd67aef7a4b35d379c13f5341236cf66b17aeccebe84d250d1b5ace9605b767b1d1abad7d226be50b003b",
|
|
||||||
"0x805a7629f90f4a8efd744248741c128b02369f3e680ab3029164faf47bb2bfc5fe02f02f4bc3c76ad83b3397335d19c3",
|
|
||||||
"0xae31a38e151d69ce1c95f26a92472c3c29a8574c438cc2766a7da55bc563fb92f190c4a2322080f3344c504c0aee1556",
|
|
||||||
"0xa2c0bf5e88743903136a30ba8b49a635b85d112c2edd6770bbad3889cb6200632fe42f44bcdafcba905a4328e98ba3ba",
|
|
||||||
"0x8f0797a0ab508ac3dab337611afb42691919caf378bd9cce9cdfd0c4d158f20dc30ac33c2393a013c25873452b4320f4",
|
|
||||||
"0x907b31004301d30af194fb1951b01a954da2ee8b791b3c0eed31b623da59c0c7d6f13a8a906948d4b70c06356cf63892",
|
|
||||||
"0xa6ddc27d9b7e792a03eecf475f805106bfbf2fbfd0e1035e52926e9c6c5e111e5c5fcd6b93fc904320b0a770d9fa06ae",
|
|
||||||
"0x86ea128a4659dc60f32980d144cb23f1ad443049f343b8f06ff7d279c8afab03cc897e6d39dd4ed202edc559de2ea5ba",
|
|
||||||
"0xa847919d88e51fdd2c332938698154aadd5e7628f7a369a21600302b9756f7ec549e583b5d8f251fc0db6e917d4fdeda",
|
|
||||||
"0x8919698051604efa30f649fe84ec5ca0f893194a488f9b88d4e40f84a8bf79bb71433e3f91e7e4b63fdb3bc1b82c5e15",
|
|
||||||
"0x944752bea5b4ed9fde39739746571e3d33a42431774cb0b3f25e3b42bfd050f871d40f1f38674e4c8dc01b82a3c6ec60",
|
|
||||||
"0xb44651084265cb025249812a12d80bd4d4d187d341e63798a3471d3a790e3dcb65fd48feb94e677f346b1f71fea92d05",
|
|
||||||
"0x8da7a161a38d2b3679a9639ae6b33e06115ed92c20bad3dd9fe6cc17400dfb039cc7e96c764f00ed03635633b2947cb6",
|
|
||||||
"0x93ac2f91830afe49e824584a8633c90fcb33a107162788e709239e73879663a6a2a0777832d5374d5b7f2e2a84cd9a55",
|
|
||||||
"0x865733ae45cc093be1897cc5196f35b2dffb03d0a182550010bbce35d32489b0e4fdccbf74070c76e17a4465308e1f58",
|
|
||||||
"0xb900529b1f646d4f4f7dc3c314133553e27df54b6e46546e08e9977f5d4a171e59154c421976610560747e151edd5db6",
|
|
||||||
"0x9037840cba56ac61fe37ec3f58df287bd71cd73918fa43617bfe426715e118daaa21694beb2e8ad66bce407d4040e2a2",
|
|
||||||
"0x84f928611fc07144a10ed7fcd58f00e47fe9db2fceec778c9fad72dd68ce8d9e52aab9a8b62e7544a8d823108e10a87c",
|
|
||||||
"0x909dc44d972c837208e21aa96bb0a75d8e790afbad70f68a30893214fd0265e8d9a804e33ed851952fa52b6f1272bf52",
|
|
||||||
"0xb8f6f6371dd30318d1d6b0fde75d2eacb335eaa5bedba11137cee60aa0e533c4e6b56051d5265402f75e03fab98abb57",
|
|
||||||
"0x85543d8fafee7869b282fbc80e4022c8c1852ad83f8ddf363c86d72316fe356d0ca707c41d59ec46a736aecf54d1ce7b",
|
|
||||||
"0x86d0674250fe443f2aad96afa74a0b6f34c4aa62cf2053810e113c5184556ab9d8bb06f2080a0602ee2ea1bb3626a285",
|
|
||||||
"0xab5646a6e44a5ee2b2eb689d8090796795ef31cffa3e7262335f9180a93c104b7873d4829724b2c81dce812555e69622",
|
|
||||||
"0x8ca50b96907f6d0f5a1018d222e4ec6442fa47703ebfad8ecb01447e367d388ccb9292e8300aa0d7053cafbe923b3a31",
|
|
||||||
"0x8e6e81a3f7ce719acdb9aa84a56d4deff67497e1c355ba013173e05d760909eed1db0c2a4b9a2957925f21a564b71e19",
|
|
||||||
"0xa1f791e32fe47e794a5a25305e284b4d7aaaa6bdebb32421ac359968a2273412da334daaa8e25443748d88ac3a9d058b",
|
|
||||||
"0x9144aed94e9f3c8a6b9bc88ccdcd157c847261e382cca8ed3bbd3a896fcdb9146f00b9f8bb0f413aa7c9f3ddbe9af077",
|
|
||||||
"0x80a1d46002224cc82258626314616fa2b1a672212adee0e1be1ebd2f4feaaf6acb909b2fcf555c72867f83566df007ec",
|
|
||||||
"0xb1cb3dedf781792268c8c41eebd20fdf67211d3a9b6972637d677e58c3da6d8aaf5f163adb1bb779a3dc73dbeb4c3b8a",
|
|
||||||
"0xa547ae87c696c5f0b7679946de05ec0c55c1b2b00fb36855b05564c2d2d6c1df57b5696b309ba131b0097dfa7c264822",
|
|
||||||
"0xa24784af730f8c666df2b36d5d9a88f6c15e3cf605963fc84ca045b93b1df4360b0fdf74f977f06bc148a7d038c34c9e",
|
|
||||||
"0xb6e456c86204188f6e0f23493629a0df21180cbc8055ad69bdec0d31d948c4fb261ed891bdb5610eedae965aa5cb0234",
|
|
||||||
"0x8b9698a450cae8ea9c5b0245bd06ada77d98ee2de47c60bcce7b59cf7d8aa411c8ee1434e0d56959e56debfaab394451",
|
|
||||||
"0x86410dedbfa4e37aba412d876838fd96d83947cfb4f9aa1a6afa96ab2e1cc07714ec6255f0b824aac1581b14f0ef99dc",
|
|
||||||
"0x89236f180a6ca981ccd89ef342494da4e8dc04f7f7e42889febfe3c9cbb3311c03f4dab9846b828a3f3ff94669516bac",
|
|
||||||
"0x8bd93d986021291acc100bb8562ec7775c5c71eca443cf682c9aab507400467a8a9f36fc739879da14cf9f8f50f86486",
|
|
||||||
"0xb9ddd4bbc087e5fd6a5cce0d21d142d497f49cfd078c0ca9c27ab0f3f5dde41001fd571ccb13eca44069dbb35643a747",
|
|
||||||
"0x87644ba29892ec195c66cc47b33724d31f9d15633e4748f4c083aa08158cdf21e33778f94e5a247b46a485ca5a79ff66",
|
|
||||||
"0x96d8bc88cc4e4b5f8cb71be343f05dd29b89ab13fdd33691aa169eea0d156af67df7deabee94314ea10318650f380765",
|
|
||||||
"0xa45fb5dee7aba8112920024ba029d2b23615af2648e1141c76c382aae6d335eada20ca0c503b41743f24a87add4c2f64",
|
|
||||||
"0xaaa7a332e371a849b6711b86cbb6d36976b61fa761a5c610ff4cc0dbcfe1dc7381eade5a307cb7ed1e6da9f6f7f42327",
|
|
||||||
"0xa486ac6f702a73c5fd7e0752d6e6962b5918962ff906ca64b450f658c95fbc48d59e727044e9405c9881b8a32ba1a93c",
|
|
||||||
"0x91dd9404e0dda11f2f5784c394803f213c6509e95e22d803548a9439ae27ae76026eba5f9f78c4ef3f73eafcb11fbbe3",
|
|
||||||
"0x949d97a470d36c554d0da958c85f0fd1621cf16d877ffc8a2957d23eff77905cec853b039f37dd8e2f1f0b40642423eb",
|
|
||||||
"0xafb9ba62f707059ec63c3a39b232faaeb5244354d766bc224976d213f825f6ffee9ced5f6e611ce3d3314976c658b51e",
|
|
||||||
"0xb3e23d1869da02c7ff6d55b44ff40edb199b8524af88e0ea3bff03b0012d55be9491c18bb97cae04f74f9356197e7d4a",
|
|
||||||
"0xaab0f191119f0bbccd6316a1761817b312245fd712b5ceb115acf8f86b7f82c70fd24cd0ac0a8617ebea09f96ba528d7",
|
|
||||||
"0xb78d30f275f3f3016683ccbe8fccd49d1a6e6fa10d06da86a4e1d72cfbd9f13866b837327ed2dcc79a81a5968c59eb5b",
|
|
||||||
"0x8a430c56f61a5fa20199cfb52ac535e6a147ab9687043f41458c6e87e84d4a57a40072d0e5a6bfc941a666adcfdbcc97",
|
|
||||||
"0x8a0a0d206e18121f7f9fe9719c13b2efc1e385f53a9188b3ad018af66abd9514d8499969d8bb58b524859904e6a32cd6",
|
|
||||||
"0x8a21a7a34d4c083ac84cbb1e5b1706e042669edc6e84bb23aeef9db6fde4a8fe22f9c7fdeb97cf4b3a19519743e73d7a",
|
|
||||||
"0xa5857a5f1ec2debaef32525829914b7a1668a3b52ff2a26ea48b0a4b0426a0355c0eca3c0c1ed92449bfdf75a39932e3",
|
|
||||||
"0xada21f5e0e6f21f4256ab23752bd48f4d48c02079c24a5ff2e7d77ffff3148de1d22694c2d94f65043d1d73e189ba3e8",
|
|
||||||
"0xab4c8587a33bd087a31613caba1c378b6e6111b47af9db3484098a04c941cabb1e500ff20b9e28f19868cc2ed3a13412",
|
|
||||||
"0xac0f11d9713e5dc46a4a43d66a048ecbca27bdb8f5f1589ab7e6ae80dc7866475379c2d7db0fa224d3ecbd36fbb12b2b",
|
|
||||||
"0xb4842ffa87ad2a0560a2939c1dc5f78aa8cb306a4e8d47233be3a590763acb099c22e08eaa5382f711b989d680a583ed",
|
|
||||||
"0xa3b3cc88fd5a5df2ef1afa6b70a7f39c97460e0b78b1594081e89d0fcbb6d69002fe2a30682bb121673e84132a73ef9c",
|
|
||||||
"0xb5319fe0d851ed2738b13fc4a6687520424c902942e72c175bab19554e70fdd397d4328dd2f67d9988742aa1d295d86e",
|
|
||||||
"0x8a106834fb0c69b90b03e3e3cd0b3b5bc083775ab0d30795dc42d7e37fdd372700776f5f60974d6aecc9d7a3160f8788",
|
|
||||||
"0xac3e836d6c610f54eed01704a05e06dd04def07d9fe12c73c4ffce98a5f99e6950632bd4adcbaa198a16e4d3a71a6c71",
|
|
||||||
"0xa5d9a61c29a8ebc4b5aeabd97c0e67d8398d66e5614f690bec37f775e1b3c94d51ae870add02263a644272c29b0fa3e7",
|
|
||||||
"0xa08e9832ecf90a010011578594374740814927d96e7bd9a3dbfb238ee470ed24cd7d93b8431c620b7a15e53427306fb9",
|
|
||||||
"0xa37765e8a2c0886fcb1c32ca9f338badcbbeaba45a0db564a752fe6cd61ec64592c637d4defdaa2fdfa6fa9515dfc3f7",
|
|
||||||
"0x8a85b0d436dfa151fb5bd2b3df5f1c154419059886fa9d1295878018ff4d74776594d7b20e156d444dcade3bdcf547de",
|
|
||||||
"0x8f3833b1571998fa57be9195519518c561a294f6e6f3e4c03053421352d72e0a9b2bc19480418dbce10c0ac5fec11357",
|
|
||||||
"0xa1cd3e2391e05997e16e9776f1446d24c8bafb296c13967eee3a4ab2c6dd24ead01991d33e82ade45b75dc97f00a0f12",
|
|
||||||
"0xb7f45ea9f523fb0d29a1353ccae680ea66304d8177190a41c230906022dd67f95711bf554f0ec30d1ee0533e719ea731",
|
|
||||||
"0xaa4eb2d468695bd198f7ce1054787f39406a2b6e3929a2173437291c274580e07e7e1c544ba481bad1e13e72924e590e",
|
|
||||||
"0x93e872b659ade13c32ea74866057438d66297c593014c25288a6141be575fe710c636d77d8e761d83a13168bdde4e933",
|
|
||||||
"0x97f754596f032abbc8e2ab8aa71ffc17275d132b2b4f009622cfd988cfdcc3ceb4c7979354481ff4c18af0a3f2c42b1e",
|
|
||||||
"0x90bd5f97e38f3f740cd1cd2863428681c9b612e2466fe0b2bd649b6c16cb92416bc78cd4d4459d7085a3babfef419d31",
|
|
||||||
"0xa94f67e0937f6d765766d48e2713008bb47a67348f80ac5f3bbf982eaf3b0356c05c324e165a1a36074acd6f2cd021ed",
|
|
||||||
"0xa29490c145a7c33ba638c605551628109a2f328ee05d30d28e37085c60842e5996f0e5e63c9d087ffa191244b6496e20",
|
|
||||||
"0x870745a995e60cfa4545f2fe6ae55045e573eca266875f5c92ba5b11f1b26ae6a244a6f75474164606a540ae950cfb5d",
|
|
||||||
"0xa3022e81838ec7c8fef8ca0849ef14490ed955abee69ab5ba48c375fe2d6e12d3d931c6ce5534841fe655bbcccf76d65",
|
|
||||||
"0x85dfea2494655ac9b82d2708da3ec3e2d2dbb0e817cb6f00e59e25ff53de21d27a0433a3268a664a99a2dd1cdd259dcb",
|
|
||||||
"0x95669f663ffe70e67af25b46eeb9afb86601e5deef3770c4dedfd6d0974175a88451a9ed847771b0842e53cf3ddd5357",
|
|
||||||
"0x8b55e41a5057d38b88712dc4a1649dabc7d38ecb996e2fa623d9d19a614a933251dbf4e895659fbb94592e6513565fb6",
|
|
||||||
"0xa13c4e9cc82631e7217d8557921dbe000192aa05e84c8038606fc51ff6f6782466ff98b320899f5632f582d48ea12b0e",
|
|
||||||
"0xa4899e162292b9cc13a4c8934346ef674d0685f8663785b9d50c9d40239ccf03e15518886c7c649979c4841ab25a0e10",
|
|
||||||
"0x94dbe72e948f1cdec8b960a2446ff6ce503d3a3f675de779426c16bf57aeca042ce2e0b094e60f1dfedc56cf9c4e744d",
|
|
||||||
"0xb488f3f8785bb01d42ec360660beb46511703930e14d9c3926ff3f9061d2a9e16bcc30cfd99f2d276898ba93f409693f",
|
|
||||||
"0x8080faf7973fab3ff4424387e80dac2ebeb6e3618c0054f29e5f2cd8ac4caad12cf14ff2f4f76fb44d2afacf78cc0fef",
|
|
||||||
"0x8d759531132862a31cac44662ca6b8fadfd848e793aed946719c2b58ac1a7a53aa8bc47e749cd70e1f2bfb4316334260",
|
|
||||||
"0x8918947325c09614b9ddb3521723a21ef977643158a859a7a5af014831a342252e7bcc359c608c212f1f186c1f564ddf",
|
|
||||||
"0x803a5e75f70847146e14e8d5b9f1c0a2cd355cfb8ddaa151ad91096be609a68773da47e3bba260037624b2cf89547709",
|
|
||||||
"0x831fcf30734803fc34220d29cc5e939a9d0bb4e83f4eac0e57c26ef7801e2834ea0f3f18efc0a5fb189f720a6473f8b2",
|
|
||||||
"0xa69b91fe7f287be29869b3462b3a312df9e10dd648e30c05a6a71f4e34b3e4756cc0fae4efa03644f2e897d36a1121d5",
|
|
||||||
"0xa201fdded1e4a5e49a7cdbe9265e3874ec46443200ac7b8df9900ec20ab8cb689e8f3f2ea6bb13e073d61afb3f26abda",
|
|
||||||
"0xafd013a39a1b2159f6249d84411923f15c458b009c639089ce17dba43e4f546ca1fdaeba62692b87caa1125f9ad61e3c",
|
|
||||||
"0x8d234bb8dd507f21e65ec21ec7e7d876bc5137614cdc745c77afd1fbac8d64a51627c3ae4de156cc6cd7fa3a4e7ffdb1",
|
|
||||||
"0x90e6c41e75238c3ba010af024f58cbb8893616f04fe5296f40a31fdbcbfe9a867969be2d89e71675a6655f774919bc1a",
|
|
||||||
"0xaf41621c6301884b11a87442d832d0a4d7c80100d93a39ca16100cd0024af86554e267a2d2a7ec363abdf732c895ea0d",
|
|
||||||
"0x8426c8051e86a4af7cc5641f48eaa4fcefc40aa8a2c42102db9447b87d42d1d380a671038ba021df8989bea77eb8f258",
|
|
||||||
"0xa8a343e34e38fdaf99c026a039768c776c6d390cf9a5830a9f9b32aaaf9770a0d3dfced4d704375f8396cfd5a02558ff",
|
|
||||||
"0xa89a5e97ebfbca0b61bd7d81a42af2c90f50e76a29939ec1a91a3546c98079262a073e741321f6c3c46358db96225cd2",
|
|
||||||
"0xb2057a06e5af142dc850be51703eb33f99cee07b4348ab39789d56c473811155bc6b64062d4f555ea8f54bf73dd69f92",
|
|
||||||
"0xb9b1e37dd03b29ef65395174814fbd79796b44ced47b0fe8b96b679893d682c45f241db1fa3aefddbc952ebfa1fe4ba5",
|
|
||||||
"0x925de4b7d055d3672fcb06437fd963a2b73c02aa7e595924e96737c456f00fa29e200b4f4589e9da1946f17c8d0785f0",
|
|
||||||
"0xb1cf4441d5aeb008b7dc186a00373086d1aefa275b6b5b778529e17e32198ec2ed9793bac75e25c6b12235e36efa2cac",
|
|
||||||
"0xb97bf89e459a049e74b2090577d49cc4818449a57d7df2d4706dbdd7532a6bc910074f6440023aeb09c6bee7e67900d9",
|
|
||||||
"0xb162d62cfe6a00e0d978ea2f6d8ab587cd12a14be2805646ae5f1b8588a340a4f8b6171bc092439157afa501f39d15bc",
|
|
||||||
"0xac100aa45c48a11686d7e2573e66723ff49b06af4d96d900ce6c08dc1ffd0e01baeada1fc5e7374caf126b50e33e57cd",
|
|
||||||
"0xa775f58424f7d8ed1a5adee9219e049180cca30b16b2502ea44fc9adfc20e2d00a4309e9a680acb2740063df3fe19662",
|
|
||||||
"0x8eb562c31dbfd222c5c2741a9e3db283d74bdecfe84b25ce3ee9e134a800445be360ca87489738e413315ac72b1c5e17",
|
|
||||||
"0x805ea03698e30932bfb6ea2687be573457df1393a7599c9d8631686a77e364337c4cdb54db14294e90366947941883ef",
|
|
||||||
"0x9486feee2d5bcef469e1b51f1a8a73bd7711923b4e126855f4dc8858c4fbc9b77ee8a56eb814d97534832e25375ce099",
|
|
||||||
"0x8d2f02f84ed2218f6f871e074d50ebe15d5ed2408d26b2ae6fdbb6984c02b0f2cfdcb283b287aa78723f4e75e7b5d176",
|
|
||||||
"0x964a3dcffcfc66c46b67c80e8511f695c6012b8f233cb18d7273d61d8c0dd27e35ff5dea42f1e1e9650e2008a6f3cbef",
|
|
||||||
"0x93c510e082c19788830888a30ebdbe92716488ee0a205c2b7d839a380cf716f5e241a19a3a859e2f80dec8a42c7d68b2",
|
|
||||||
"0x9376c105d2eb1f55e39332af0d70e587be9ef398e239b49b01560fefb0c950389761cadf39a32e324d71859c317e1788",
|
|
||||||
"0xb3fd7ba1d7401cdc8422e2f8fecd745dea850882c909e7b812de4067fcc5f745bd252c5132a9f83c1554c1dfa4c66ec8",
|
|
||||||
"0x958ef935d5bc06a876498d77a1cc2950a7f6990bf8f9d4ef8866d387f800d74b5833d608124a413287d063a23abb7093",
|
|
||||||
"0x8b338bdb7a1114f13bf88d21ae925a4c8487c96982e9e0f08062a11603aaf22273fdde9dc8807286b9bf91e2449b8ac8",
|
|
||||||
"0xaf273415edef8bd34a9b593531bcb6b631ef3f8f2db78e9ed73596d3d6943415df5bd1a587e99c378510540a12109e64",
|
|
||||||
"0x8ac7b7421acd1962ec8a91c8feee71dc245578265a95dd24549a86e8a3525b608c4dc08c5ed7dd79c78cddc41232c74f",
|
|
||||||
"0xa85c85174ca8b7bcbbac0e2483514326bebcd2f489fdeefce7bab04fb78c92609c364a4c2b3226275dabd660647eb3e0",
|
|
||||||
"0x8e46723134d662536ded0356d98de24629d7d182538a13143af285ed001f77c6d8df18b1a64e6f3ca0106413ec9e7afb",
|
|
||||||
"0x8b23bda51177933888aae4911eadf355cc94d8c4a84eb7f448f7ac3189557f0fa5c4be4af851abd3eecaa7c34fa30b6b",
|
|
||||||
"0xace70d08c5b8cd039a114175a30d8403f2255ce8f2f558b3d421333236886c1cc5afc8dd691d265b398689644ab10395",
|
|
||||||
"0xaa5b937a8505a3e504b37d5e4f7b36dbab2dfc70e18afb97dee96912fcd89997063df8ec843bf121c9bf4617eff7eae3",
|
|
||||||
"0x9977305cc2e85a095d06ee266d9adbda6231a9daf90840e3fbd835a36a6864b34909a06623f972abbcf863094816328e",
|
|
||||||
"0x92c092833083640d786a37d02c61b624243713734c3da43f75276fee4b40d2c8eb965ef2e9d9670e211a5468039886ee",
|
|
||||||
"0xb1c7da2b1303369115a611e872c7a399aa4ca5cabd404028dd52072e01ccbec5b2dcdf9a2e5849b138bc72022c810ac8",
|
|
||||||
"0xb8f291719beaebfe457bca35b64ab848e9cf16d0a3f628ceef371b370e51aa22d76af65ac5551a23b18f4a39faa4b2e1",
|
|
||||||
"0x82b589989909ed2c6d1d32d389d8becd01200425539d9051f29f54aea94221e7f1b272015f79dbac0515d1e4328e979b",
|
|
||||||
"0x80d0cef079e1e8f6a780fca47879fb7034061c6c06dcc2693a495b706a9802c53cb87d576c262fa08f435aa32244db43",
|
|
||||||
"0x9577cc69857028913d17fceb515add33b0dc600afa3d9b63ec3c6968d82652798db5adc2ae38f5fb2c0b7ac5a5f769b2",
|
|
||||||
"0x8a511d3ba6f7b4f7a711c1e60d2636c04b522da38875345923b0a7bb678c3e82590d0267b5263d4c12bd792f4f5e132c",
|
|
||||||
"0xb08b7c5fcb1a7547bd4ea9f41f0866fdc85a9aa61033d08082c9e30665663d48ccd027e1cd7d10510239fe69f990307f",
|
|
||||||
"0xa2e899191cd1c3b01a7466610c2b587fe618996b220599c63484c4ee529af1151782a94c14c63496578f1df3104811f8",
|
|
||||||
"0xa1ff0a31b2230b4da5a0dec6e319d00083dc41d79b7c47c82c82c8605b0d9691a35d88b10fbfbab8c59e46e9c4080b4e",
|
|
||||||
"0x8d430b795e6919b6badce0b418951301f93588c1b054ed569bdd48f5d6e8dbf75d02b4377ff1f8b95560cb4ba181c2c3",
|
|
||||||
"0x8624dd4a9d5098044c09466d33d5b66b435073bcfd58002ff7a0901031c3594ba650762d6ffc88a3a8b94477205fbeaf",
|
|
||||||
"0x993e49e0b4918e68f6957bcb5f53ef20af64b3aadee53aceb5a14fc298196ab93549718b54e76cdd75451e8b301497cb",
|
|
||||||
"0xb1a2f00560c27d32a4ab3f396ad40f1e62aaa1db4deb1c39006dbfba03489debbae7458c76ad72b039fb34c31117ba34",
|
|
||||||
"0xa9882d23d069a540ba42dece3a39e1f233e3658381519579120e42b500c5dd3c8395512f656ea7e78aba3d73083092f0",
|
|
||||||
"0xa21f9fd8759ae47fc212bf3580f359942bf1b3b4145eb638aed042a5142501aa5c224dc497032476e78007a491ca2459",
|
|
||||||
"0x90d6a0201f12f1a47ec332d8c516faf10453cc422a34252de1560f7624a0e1d9c20e3a53b42a5677e5b48fcdd31d7935",
|
|
||||||
"0xa16ff223344ee621c16899ac061beac3a582843f6194e5a02bbc9d874ab2e7e58012c0e144c02f7ab23091ddc10827d3",
|
|
||||||
"0xaa30c72c04063b1d662921793967e7a5bb3b97ad6f677d7c7f4aa4dd72315f2c3f8d85d5b6ed33905f49a842734fc4c1",
|
|
||||||
"0xa046a6611bfa2321073ba0a4dbcf87cf0b3f94c5cfa9e5e6be5dd90c0cd29dc5d348c4f6f9167838116b3111df1b5147",
|
|
||||||
"0x8b886a87b701b9a57b6be2a9a5903c149b8710cc7523c5127f71948781135fd0d56dd1d22f2ca527a9f69a41330adbc7",
|
|
||||||
"0xb3348862f36087d86c408888fc6fad04e77f74110b78fc34e15cfe30856c1647534525b00a8592f90ae914f44b83e641",
|
|
||||||
"0xa0d27975bef72262f888ad6428267cb8ece0a543316a514955b44bdf0886d2ff0a9e580f5211ee70cf385e1563c06027",
|
|
||||||
"0x96237f46287c98839dfb2b49fa6dde42c5ad8a7a1a6a99d0015ac93d3e053ab926b993c5f98c9ac7f4e94caaa6899ef6",
|
|
||||||
"0xb3fcf279120a43fd4e48adccd42ae8290acc86991372d23d02040e84d7949ac397bde6d4241af6db509968b4fe7990ba",
|
|
||||||
"0x8457306541e0d888c6c5b6d6d141a102fb3d8b30e279fbf49c9c8f9771b28eededeba5faef0f5867721f18d4e1918da7",
|
|
||||||
"0x9308534c4c250bb7f82a3c9e44ee5d63ced5e2871acfd06b4b24517621ecce952751b4be0944ed822c3a9fe6bb4a285d",
|
|
||||||
"0xa3d3475c09357bd8e07d952c4f6ab4256eb029d427f882e80fe2109910b88af8f1d18da9dd5bdd895a8925dadfd40a14",
|
|
||||||
"0x91381256ae84f35322b2a65d924afca1cea930484f55f5f4e65a9d4aeb81626add7cd0f9e59ff74f69f523466d386e64",
|
|
||||||
"0x8bc8747ab34f8658d0398a2bbeca402d6c1ae9b5dc77a7a90a139ece106f34ef81ca069ac82920000ed887b6ec7c111d",
|
|
||||||
"0x8ab97cb412c0b5b436f5382cb9068c2af675bcbb4915537cd2161271ee658f779ad46af5fa2db3a09c6234290cc867e6",
|
|
||||||
"0xb31ae2f6e63e8d5f9df27c07ea330e244bf332c584f7b0c02aa62793796e28ef03a9fa96291978fd0ad27130628aae2c",
|
|
||||||
"0x92325d0cc40757e5de4e2513d714ecf4bc39e5ee3144c257d290e06c7aba2199d7c7fc0013438cca5d1ed3e057cd6662",
|
|
||||||
"0x85b6c7f3f071199de43235311a369c92c332ee522e9a43da5759615718695f03b7a7d1113311e85a5985d4c1e65a28c5",
|
|
||||||
"0x94034a9a0e112bcf064ace23bbec3e31e3691dd31f7c20813523f12f1a727d1f87cee4d550f074bdcf0b0c9ca9dc5240",
|
|
||||||
"0xb5c07216d45b69e1871f3e2e92326ef3c3fcefa313f4214a682853e7742173b722004fe08a40ef61ff0079ae2cf677f7",
|
|
||||||
"0x8168732d5f9a050a3f8aa46adc05ae570c5881544d7a48394a3e4839a511e2ec3ccf4b05112bb057713f5eff36ed35a6",
|
|
||||||
"0x95b234dad219e722070e7da16b761c8a28eeff074ad8a154ad25b8f2c9a8dee34778479ac61bd51bf1d4c2aa91df8b82",
|
|
||||||
"0x826a4fcb0eee37fe69de350e523a898bbc2208ab6973c78fbf5f28714335343ed682cacca1f7233237cbbf4d0e9d3c60",
|
|
||||||
"0xaa3cb3e4238c6cc359224f222a7b2498fb74bb8229f449c79d00abff7d699caefb20cb0e7ab284bc7bc8e161df206639",
|
|
||||||
"0xa400558c0e1c36c22b251f05716a57af8a7c8089871f1dcd84b831532b3edb56573f3dd6a988d2c55bc0b013e0164ad7",
|
|
||||||
"0xa3870970003cd3537a1b0d0db691ce32d84f556287fc7c5e688961c676efe81d91024027b6d2456ff9c315c10f94447b",
|
|
||||||
"0xa32aaead7538e44b5ec52501ea42abd8e1f6c158db5ce0ef6cb0242963c2d7875976cf38b77d13d326cb36914152fc29",
|
|
||||||
"0xb9b665eb858c4353ad64332624d5bd317a073f88e41cef4cd1c1517d5cdf7fdb40395ffbcbcdbe31dba86ecc7f37cd69",
|
|
||||||
"0xb7707bb96a8b0bc7a6c7e6cbc2224b8d79fc80f96db45cd48e6a5b12700c2352e3c5bd34e559bf2fd4b47f1a12f85edd",
|
|
||||||
"0xad7d4977af9e831ef85991fab8858139c10f28ad40e888e23b7adb6c9e7facd52b38d8978cfbec1c57b9346032b7356b",
|
|
||||||
"0xa1d450c53027339add61685b096c41b45b9b9498b6627de8b072167c5fdee1127e9accc3b30af51a9890be0db54de234",
|
|
||||||
"0x86b0221caf110587b1aa27a0a2340cd66356a43896581d00bb7cb310e3fa387c56d03ee296ca73467047ebe0bca38e2b",
|
|
||||||
"0xa759f0b3a520717b3aa3b193276d1dbe01411e7433a03a8d5e48c4b1e6aa4c98e3d7a90084bd696137bd6de5cf53d1dc",
|
|
||||||
"0x882d19da7ecb524f2762becaa73b070e659d7bec3a6e05405543ab6a6e42e134dae2f5bb614c7741b70ee87f7bc63cfe",
|
|
||||||
"0xb84589fe4447c3aa9d857f2218945b6156527287d31b36043236bc779b96f36620cc76fc7f66a42c4e1b5942ce6f0dd9",
|
|
||||||
"0x90f98c0462b1c2653ce9cbef68a18631a2466270a33ccb8c7bde2280d3a476943caa359320b4dfb564d47baae576cc89",
|
|
||||||
"0x946d75e88c9fd69cf473d311e9e9d5b4ac615e1ae1394066f55cd41e40f623e4438bb4e185942f2c98e0a35117289003",
|
|
||||||
"0xaf4ccb6e0f27a9d023477285fe15106fe1f9bd0fc35643c07a653cdcaf30f3bb6a91a2599e08d7d73a3aa74ca81ad174",
|
|
||||||
"0xa8b3622f97290ee4cc0156447987306a22976c2d5a6c5a35ec9bfe9adb591199edee8f7267966a656d944978101379aa",
|
|
||||||
"0xa850201489281fad169f4911d5d1bbea5d1e53eb65feeb33c86de716838d221834b0b39adb373782227d40daad772913",
|
|
||||||
"0xb77aede1e6a4aab734a53a7bbd1cf4dd328deebac27361155a1719e682bec97381c79d9adefbecaa3a244cdd78afbf7c",
|
|
||||||
"0x92f57406af66fd1232cd11938f20b87311e6765eb9e84e632f6ebcae99d219538c8f02c72fdb67ff035205bd9545c54a",
|
|
||||||
"0xabe544f9baae40dbc866e07241d3b69bae9ea3bc6a5250bde7c29c3cfc1c3a66d8502aac438fe11bb9593bc523a62055",
|
|
||||||
"0xa324942b374e96ed23fb946171b4fa108cd1de6e2ddc81269134279a8422926b34df97e1a86f59fea9f4f4fd5b3fa56c",
|
|
||||||
"0xab0244f85591da41ae6b7ba8eab39ae7486123748997ce811bd64d9a3edf5f8f7333e081972a25aa5d54c85de58fa0f8",
|
|
||||||
"0x92d0fe78f0b39f3dcafd8d127008262e4ab2a73fd4056eaed26074d47dee5a14973563b39210504481710393da9369ea",
|
|
||||||
"0x8903c66bbbc31d472fc121029f9ab2eb5fe5cecb76b79c4cf41770b13ea9ba8ffd3457eb02b2a9357a4b95e4b34daf0e",
|
|
||||||
"0xb13f55bcbe2b7bdf277596b5946998877a61ea9d4856ef89ba53a7a2e30f0f4fc09bf8890b6154fa99a203859449e347",
|
|
||||||
"0x93a3d7808dd94434172f4848be6b19fac64bfa44cb876e6541e54dbc5a7c85ab02eab556c7b5893e6d4e770bd495d94a",
|
|
||||||
"0xaf10d2b17141deb640cd4de9c4dda8c99e15d0e37b4ae52fd1458c28d07e5b1f9ff1304d4a9ee6afd2e728ec9dd0ed3c",
|
|
||||||
"0x85839b47f0666be16cebea74d7d6eb6ec5da98f4c24384821e8aca25fec913099d1531bf531806108abf3edf6cacb226",
|
|
||||||
"0x8d785392212fc074ec334084f3b70e16655897c7e2c8c142684e51187bdfc8ce7764eb3045b98a38f6fd8837b6b06a6e",
|
|
||||||
"0x97629511182f8f17fcb413e3ea1de9f43d7018e7cb89299b80b39d613024dc2723b0ed1c867301cdeb093f4e22397972",
|
|
||||||
"0x9295f4a46938606af852fd8e2a1e74d34bcd34998f74ab1397d30c36ae09209e83c1883f01866c66aa583ec69e24bc60",
|
|
||||||
"0x83e2ccc4210d18fb8b8ccf934b16f9f215a1139a8af3c0a115f3cbf973a43e1043c2e7a6aaedbb50dcd60179c0d71571",
|
|
||||||
"0x959e82159d314239308a48e9094053a7a12e667b936b9888483760b86c8deb050e3c5ef703c140f5457a51cf443be8d2",
|
|
||||||
"0x8b7baf0f488f83bd46ac5d02e8c3dd615cb78d9576d4e62db01d247dcd2cb15aaf1a8a0253a087aaab49e3c04731197f",
|
|
||||||
"0xa81997d6fc06d45d544c9069237b3aa7a5538a8afe9171c49e711a54bc00ca7ea6d9b0332f2327e11c1235ddb8944e93",
|
|
||||||
"0xad8bee10f9bb91a29069b55ed79c0f7e0c31f574ae133f99237e4031e12e19611e4e98a4a0d180ac9c8b8e8e88db9687",
|
|
||||||
"0xad9e6b92135e4e2b817f45dc2943ad635da76ec384f2818924d53616de36b60ed62278e9325d71f608028c5df05697e5",
|
|
||||||
"0x86679fb63540f8b3190dfd6baafb6a1810ac2e4486cd0c80624ed5faf811a0e4df51ba4e352d85126276b6cb27ae4e79",
|
|
||||||
"0xb269ac860ae99194ea92aa7b68a1ea7564684be06b23364f28e031521297a05a60ed44fc79b93634fce2429c28bca972",
|
|
||||||
"0x9910e52b68a1a97a874080a10dd5687a2e536c7c9106e65c16f45ef6fdaa3dea20d5e0089f000c6005fc44af878c7283",
|
|
||||||
"0xb91859c182d3b713b14743ddfd6da5dee1f0f7aa964a2b79f4f2b78218f550128dfdef442a9f8df8a91c1bce494ec1a8",
|
|
||||||
"0xb96dd044adc70aa4943bdb911c10cf1d69aa78da9b0753e9da4ba274dbc9686d5bfdbe444a9e9983a42f77fb545c225d",
|
|
||||||
"0xa10dfd035212ffc6a568b2cb4b6f2d9a501db926f686b9c6932ebd7ac64a608f23bc1102fd0ac3adcf36279e9b41f906",
|
|
||||||
"0xa0057a101105641b0381dfa5955dcff7382e72659885056b88ce399d50c1706c340909c7769e9c02304a133ca8ee5898",
|
|
||||||
"0x96102cb239f7358b06029ca15775383ad1d438e4956fbe3b99848d069494ae472d1d9bed852b041a224ccac772014d2b",
|
|
||||||
"0xa741c8ca68a72e14e379b715612f32362dc1952017a710dada744f8866fe7b980fb653560f4e83c4c616eec7f9094db4",
|
|
||||||
"0xa3409f9b99f39f7ddd567f6504b2c3bc8140bdf0d1e635a1fc8b0c7377183c9154ab64e8f5753c1ba647e006718618f7",
|
|
||||||
"0x8c32ad2e87b0146010f2225dd182e88c0c70f128314e3d7032dc5baaa1713a0c25a19cc9169532b820e0aadd15e991af",
|
|
||||||
"0x85433efd595dd9f49b2604d421fee903f9a49af910068bf92c79f938a3001404031c00dd92bbf2385cd2d884ff428d9c",
|
|
||||||
"0x86cebb07a6b312b5fa55c5ae4deb0a726f85a953ee16947ae38de316b84cb7e307599d9964d4bc41c7e3e3eba50551da",
|
|
||||||
"0xa13a4db1aa6e6c58e1ea4be8a8ad270a326e9dccb1e20922b5a131d46184b41ac5559bb56516565967fdbe234afddc7f",
|
|
||||||
"0x94a66d71c99736ef434e1edff5cfd15fc03cad9222dbfa6b9f7b71b3259bb01f0cef0759959710fe307f19e7eb8a50be",
|
|
||||||
"0xb84b94694295efee1616f5b1f078249aad56f2be720593edb422909db286b4afec85583aba79ad2161610869cbb7b23b",
|
|
||||||
"0x95e24ae991a496af69bf6ff2e98a218c8fbadcb81997e2122259e00e1156bd957bbb7b56e63b451499307cdc5aaee84a",
|
|
||||||
"0xa2cf774951105d3ec53c0ab329c4c382fbeac36f24e2f8a0575c988eff5d9cc42a1c4cdf58d5fe867eb11e2b1d4c6ede",
|
|
||||||
"0x99680bdaeae98104c93a0fae79cba5197529a2802c06506758e26e41ca9c2cfba3727fe56476b329153d36dafa092429",
|
|
||||||
"0xb18ab0d63b90ffd798b3e2f3c8b79be43c85b4d8e103526117ee3ba9d14a486524e99bfa19c1c13877add76ec21a077d",
|
|
||||||
"0xb32f88abadc42328bafb33eddf360cbcbcd27397c64120dcc90afd4c80c80ec6f97b3a1c0a6e25b98e26b1e0a8dea903",
|
|
||||||
"0x8b8197a841f9893f0275296fb1f57f5697de753d8be6ba3ea76641358a5b0ee5fd23d16d53f4f459759377979eb9741e",
|
|
||||||
"0x8d0dd5d40aba0a62bdbd192b129f13e8203bc03acce08b09ae705458bb8c15719f8de6903e37f6f2c3466df1e8396d7d",
|
|
||||||
"0xaff3f5e10cabcc9fa6624e604d26ecbce7fd26d0b34b057a21a964ff23ade401e2a6cd77bda988a2b6b2a4c36fa36ee2",
|
|
||||||
"0xa338d853d99d56d53059d31de905b42d5b5480b5994082f91d2d87aa96faafabaa0efcc27ea3a14c768618502d19dbb2",
|
|
||||||
"0xb456f5b0b675d13cf537a397e9f7ec7d76c51617dfe01fd2eabd4e50b531f52d4bd66b585bbc3f56c8021b1927e297cd",
|
|
||||||
"0xac59fb0e115d239d86dd1c21bf66beb489f4b82284beed54711f229f979738faee0e6ab330bf1c05b9d9b669015f396e",
|
|
||||||
"0xb9f2a77117913a5c5378a5d4d59c6ba4ae380f8dfbe9d483a43a88c54c013ec4862e9186b8cfd06ec2f83cb077ff9316",
|
|
||||||
"0x9539bceca5d4de820da497357f20a4674f477de4b26ffbee6bf08dbfff8c80372b7bd192e24964f913bb2dd2a3d3c79d",
|
|
||||||
"0x8fa6acfbc88c8758e6d6dcf14be47b05ee669d9f763f86fe41c3fce243a126c5cd9c7f7034292b8e9b6f090d0f39cde3",
|
|
||||||
"0x86066d43fb75848eccfc3b43bd5a7a4da0f874d25052cca08ef93dd029e415520fab71dd315a76555b7c35d5c9288206",
|
|
||||||
"0xaa151bb710a3854ba341e4996637af9f478f85c1c0d979d74660503b16168c37ef1ac011818163ceb92438ae15f8002e",
|
|
||||||
"0x80ff5ea2842f428ad9914259a59b60860bd4e6e573c3f47d093a1551887cb6739ad8f590be086cc25f3178defa094a2f",
|
|
||||||
"0xb2d2c18d9457ba1606533570937f8902e5ec761e8ce8f9784d6632e4253909fcd4bc6634c410d219ece1f736ede92baa",
|
|
||||||
"0xa3155bd01319dfd6bd152ef7a349c548027ae853809e6c8b115889748d45ed627ac81fb7898aa81225d902bdbeb224f2",
|
|
||||||
"0xb09d4b9868cc7c1ea66fdbe8f129713dcf20ffcccd3a2f95513025a389efc24478ea8a850dccd74d1002f904a0f45072",
|
|
||||||
"0xad01e82c9d49c980f4021b0662a03cd740a97824ae03982fba066e017956c370d954119f6c8a353debd342fbc3c3d46d",
|
|
||||||
"0xa4300dbb6154a5907b55f74517b79d9ae1af452e0614768447ebcb7c48cdb5e91758d363ebf9510f035c135315afb081",
|
|
||||||
"0x897c9855dc4cbe4c2f2eed4b21e5ba3a009f2cb813919eb638083c3bbf2fa7f6bafa59f61bdb083c05e061126e1efb84",
|
|
||||||
"0x96caa39bbd5d27208320413015be1441e3c990a0fe997692c27fa0977e979e2d9073e01cb3247fbc92fe1566dc217c78",
|
|
||||||
"0x8e7270b698cb176292569516f62be18ffc315e97067a410de613e8586a18f7e19a11b1c40e7a570e36534e7b4762f0d6",
|
|
||||||
"0x95b82aae7d22fe483328d398d75cc73889cb8ee18de8256d9fc2279964cff2c20ae636f577e76096eaced79be649c668",
|
|
||||||
"0x9368f97633651d5ee902d87b431a1d0162f349896b3d8ee5a897c647b898599d2a0026584b7311438a918660a2ea68ee",
|
|
||||||
"0x8cdc5af25eee85daa46c79d4dfc0526b0c88d167f155f848469fcf7e3727866a67b0451eae2443abb8a91f46a6004a9c",
|
|
||||||
"0x96046065bc1a9b7f2c13f2aa5e573c344090da66eff7d0ca91caa49419388b8231102bd2e10f8c89a168d91a392b1728",
|
|
||||||
"0xb4f88e34774dfb2755aa471720a984b70ad0494a8b835d9eef732b7082fafef76da60a810a878bdafc6fc244e1ef08b0",
|
|
||||||
"0x826f863bf891a98ca3a82dff3950a0a0335f4d635b2d46abca7eb14b3d47038d09cbda998d6e3851bd41001e33db1482",
|
|
||||||
"0xb56dba2acab7be7284712a20324da40907a9210d78ddec77127928ffcc2cdbe6a7a5647c1bef055fa89e0793add4db00",
|
|
||||||
"0x84d66107df60bda44425262f1793d4c92b8584b973d4838ad222637be5132fc501431b7201c39bd8287327c1f18935df",
|
|
||||||
"0x83f81afd28b343a82c4b88c6c1d0db7258d9dbe4227001073cdeccdc0f2dc1b84c3b40db96414ea50d3f23a48bde5246",
|
|
||||||
"0xa90c68f55c1c77343c5251246ed431c0c4b3ddac65c60a75424b59ee35bf2d3e110d71a0057f613d88a0f848290b2c6f",
|
|
||||||
"0xb2431c6719cbb23ce38e76b488ee4a736838bafa76b4447ace74c667efa412b13a27b8e08fcf5d4d279db8247bd5109b",
|
|
||||||
"0xa8e6d94483a12d27ad570f199a9ddef6d70ac96607a08fa329e913722dd1093c920efccf47f9239bbfd0a3156603d0d8",
|
|
||||||
"0xa0416492b181729ee18066905a7af555f839bdeb30d31019c5a8822f0ce60aa9eb203bc1a144fb7c1c4af171dc2077f5",
|
|
||||||
"0xa367b6caa6497183e91c96fb0f347c638f10af942c29f03e7e127ef2ca61ac8acbe18022c80088615e4099ff2c6ce600",
|
|
||||||
"0xb4ab6fa53ffe64a61b1a9f09ef54c241b3f3f202966ae760deefa3fc7932821a394c3fcbcdc2f8d0d44498f961b5b196",
|
|
||||||
"0xa72eb051b039505d1959cf8c4574ce4be9de70b8b1846e51f98c2e086bbed3d5213fa5b947bf92084610139e1ef4df56",
|
|
||||||
"0xa99862e9b6f37da912255cd4dfceec3d3879766865d1cb7f80c823b22ee542ebefda6cd6e65e1c9d0659179420041afe",
|
|
||||||
"0xb4268aece8f8039d719bd002da7f1ad2b1b148344c4269c882ba588d744858bf8d7ad2a339c2c0e90c78043652a65187",
|
|
||||||
"0xa19497340f145a0fa19a488c0ec54908b66edc213f120159ea11f9d60f32cc460835f8dd2dff622cdea92a17214c7e19",
|
|
||||||
"0xa18648500ef9da6fbbef4cb6ed8f5c2dbcd461542149c7f34f3d2ace96ba0af6f938d700cdde0484d7cf4415ae17e49a",
|
|
||||||
"0xaebe6ad5614295905967bc90bdcedca109ddbe92a06a4e7d184487f044db67ab4c300e9bc372c9ab717d563cb7902799",
|
|
||||||
"0xb0cd81b5b26bd618312ea68e481a82432a79515e041e4e575089c5267059367941bb773d81673fd264fd6939695639ca",
|
|
||||||
"0xb6d7f9796e87d926c4cce21a2461d7aed5186219a638baae776d4b2ca99d4aecdee5e00f74d201b6f7a26e8a82b03dd2",
|
|
||||||
"0xb270337e07e1b25204e18addeb13a85686906ffc51996405198efe4e827682c7de3a68002932f9be95af0f149c49e8b2",
|
|
||||||
"0x84e47e48cf0876f2fa73cfee25612f1884a6f97111722fc4b2387ef6a6eae31e82b2b9aeb99566e8029928f2ff3b8f27",
|
|
||||||
"0xa961ee0ca2ea364a922d526bd4fb81949a952db511fc13b2ce8396ebf53ca7ff7ba60654373d4fe6ead27f1a7d07c5a1",
|
|
||||||
"0xb2779d398c462262fb1b5ecb4ebc50d00c44337e6ed20210ada4b8b6321b99ef262455e50647a61452aa2678e64def15",
|
|
||||||
"0xa6beac4c37c76e0bb175795d366178ab96ae0a3eabc1765c8983854822dc4bb11e2326a41c0847bcbfdad2c7f8a657f3",
|
|
||||||
"0xb9d7cb3c48d41a81e1348b21a3bc05d380a4e44fca5030f3926e390fd4f818829fc4e274bb22e1c8d5306ca9310b40d0",
|
|
||||||
"0x949b56bada8fb97e1aaa6a63903aa7fae44f412f9babc52671b78fd2fa3d1547ade93e54ebbf454730779434408d639a",
|
|
||||||
"0xaf8e0ebf68cf4f49984f29b045f9cce238cf69ed2d36f8652b3ebd7751716978795738b7113d6e3f41c912f31b5fcfd0",
|
|
||||||
"0x8f9c926f14ee78d6d4fc0cf19710f4dcac8c72e40d333790917eb7e233b60628fd7e5567019885c0f2e4e454f7bb7073",
|
|
||||||
"0xaf9ab693263d93cdd9e477b6675776fbd59812e11674fd44a5bd7049b6c8776f4480660d2f44dee719a6a98150fe4aba",
|
|
||||||
"0xa118ec8ccccca09b338e913c0acf5bb917926bcf2f48f52ba97f9971485b6b18cbcac42361431fe4614b2b40a520299c",
|
|
||||||
"0xac58b8a0d7d19a3ce3087f2b15ca749e857a2585d1b3eef55c117dadd491a0a46421e4dfb74f89633a1d09678a6317ca",
|
|
||||||
"0x96911acde9d9bc39b083edafc4768301c19f50dfaecbc3e0dd918ebc3e2f8c91b8c2d33bb54680a2f6891c73168cea5a",
|
|
||||||
"0x90af3bfbb5a7953639ed669914b13a8faea3dcf4af2c00c112196a4de4beaf2fbc83d1ba72ee1b9c3749cc0e5d87681e",
|
|
||||||
"0xa305e5ca2dc6015c07572a5985d5eb914c7561ee1e444f5df9076c7e9a38b11a95f28db3b53feb4eafc9ddf7d481d451",
|
|
||||||
"0x998e2106a2974c1370b012c01fd2e4fa60822e9f590be486454cceb7ffafed60858a8faa667c9285553819aa46a90b18",
|
|
||||||
"0xb0faa71cb1173a1b1bdbfc404f1378325935025bc802a5e210bdc63006aa9d83b64439e0e2c82694ccd5488f919f7c7c",
|
|
||||||
"0xb8ae8d18f5d8a5b7bacf03329a65f5d5e8ce46d83111a58ff8dc045c2d94e54b26c3f79a2c096c822d3a0dd81fd56e64",
|
|
||||||
"0x91a53eae8476239a7fb2e10ff05be47df93e1fbe8716d2d8a6568323c3682e49caa579c9348a9c59825dd2537f39b416",
|
|
||||||
"0x92ebf45084b9648ac36ffe91b2c6f15ce42aa5473b7f41e5d308847305fa2a30a70729449736d68ebf9e219999949613",
|
|
||||||
"0x89786449560b57f1bb488b0e0086428659801b0c7ff3e87d47bf9daffe3ff609d3a2b77e4a3e884549ef70b8aa16e1e9",
|
|
||||||
"0xa6df5f0147272d946d8ddc07a8a3e50866405a5a8b9a6968374a8550a8d5a0ab355f36bf65ba139d3acde7099baa803b",
|
|
||||||
"0x84af618b175ddc21598b33e1c5469a6b45f312f4bc27d5881479391cd20ff3b714228893ab05b81f40d88cf899b073fb",
|
|
||||||
"0xa13973bd367f5b4b9661bcc26ec6dc106d610e96f0d5dd9d0f451d1dfad0acda5fd375f6a522fdf58baae20e133827cd",
|
|
||||||
"0x8bc9e32be12843787401de4b6514e038ac608dcbeb2c4732d6bac5d6a6beccd0c41400b9f0a1fcde08affef34618ec68",
|
|
||||||
"0x826dd5d136d74f6eb10bf6a47f9242773830f71397919b7e0b42d53e18220a3532e8c730a8f7c6472b2aa2b48bd71e9d",
|
|
||||||
"0x95b39bec44b860590c0bec77a9c80519cd938cb094620fca4bebe2dd7a578188359b915f4b29103eb76807a4d0c11c36",
|
|
||||||
"0x8d5ecbcd0bb59e8269e3c50003455031824194d913d591e41a449d19f1e0c5fe0326d53a0d06957dfb8906cd4a75ea86",
|
|
||||||
"0xa6b0032fdb9df2334b86212046fed56282aa3f038b16814cd0b2806c598af57e3fa67786178b94b90f02d7f9acbfa5cb",
|
|
||||||
"0x95fdfa19cf1403dd221a5f0b7c6a38f9f171d3a1f4074f7cc272cdc96963b5aedc9eb72806c9abec3ab743d13a281cac",
|
|
||||||
"0x8f33a8ede0da0832b40b65715c4c2e0056d33f432e8a0899ccfacf321923a30a99468e6b83b8810be67dc95558861b64",
|
|
||||||
"0x89d7b1a8169809bd347259506fb80f92abc42a2a804b2384e29ba6245965c2e53f22018b691a9024dc87368e8af844fb",
|
|
||||||
"0x9793d3600db605c285fa413c7f796f62e79f3f599ac2813ac48bb74ac000180a3125cd1aec74d0f439ca45da3bcdb699",
|
|
||||||
"0xb93f409beff63139669695e4e20919bd3665038712fd63fd09a07e47bc8f924614bbbad715c55d6b393a14cc97e705ff",
|
|
||||||
"0x82ddb743e31b992a04a5ee6f6235a771e18af79deb7f3325fd53feeab7699f3bdcc91769cc4f4a3064192d6ff73fe0b4",
|
|
||||||
"0xb87a4545c62f56ccef2dc3cc294bba393df4b63a67bd6fb9ce41568aad4134d66f10105a41a43062b7ebe805493c68b4",
|
|
||||||
"0x95d60dfaf2dfb9defc0fe7555da49a68b847dc289c076e2639e48e11f116d0aebaa816f34958dcfb508b14883ee014ac",
|
|
||||||
"0x82c3c6a7fb9c0800179aa7fdd6a749f47177c99effd22b278efae2ffc0bdf046e087222ff3b5b911d037b1ff4d547c10",
|
|
||||||
"0x97dfdab2cf2be6d3719ad64a3be77a918a760ebf5287724097708a45833d4f82d868aef9e8ef5a4535bd7f89e18e7ac1",
|
|
||||||
"0xb7da1e4551561b4d1d5dd010f6c1ff7903f8a07ae9fb18fdb36804cec70c94dd7250141d0ed40fba025e9502af731f07",
|
|
||||||
"0x9749ffce13b359ab07a0aec5b66c302d6322f83e1696acfea333c823bbb99a8d9e81dfdad6ea54fa1bd7f8707173e399",
|
|
||||||
"0xaea9e0a4a251107cd9ef36a1f530487acc2daaf98169e602b15a46308286a7b1dc86cc5f1925e65ae99104bf11b63f88",
|
|
||||||
"0xafbb3f4474b95689059ff263013919e1d573f1495d23deb31cb2755f6bf9611f037366b144d7553b2a366fd1a6bfa280",
|
|
||||||
"0x8e760ace7e6b443b9655b44550946866495c353738bab1881eb9661507f5ee85108641a8e1a3217764f02b36f4e27695",
|
|
||||||
"0x8036cc56f2418f3cf027cee6357ea2f9dfbba27e21b3faf86e164ec1908e6e8ce89e3bbcdfa34ef5f86703e41f0a6164",
|
|
||||||
"0x8449b61c699086cd8b8fab8e79dfe5c30b9d1f0a29ba4b5b629e0f32f52c298500b5aec9cd07785b6345c00c197db3b3",
|
|
||||||
"0x8c90d9a3412dcbf2a0aa937ec9737abd5466712685f1faa556216a0caf2badbadca99c489cacff54208bb82ded592691",
|
|
||||||
"0xb1d383fdc7df32115dba9d25bbc1ba24ae0f1b5aefac87a513bc0de6444f00c409038b55ef826bc0f694e62fc1109b4f",
|
|
||||||
"0x88c00a4476534efcf45b74c29287f13827312bca83a1d5b487c359073951eac62a35dccd2fbd9fee5a956da29fa51602",
|
|
||||||
"0x8cddc3236f2df5064240e1759104370e55dcac8d592490e6e17720a81297d6eef5f9deef17a9b7b2db818dd015a8e206",
|
|
||||||
"0xa2b0fe20fe9fcf3db612ad3b1427310b23977ba9e7f58228daa0d32923db0f7319bc03d7d66ad603f927b0653c662977",
|
|
||||||
"0xa8bd0a1bf9d724eecd6d342e06e60c01fd9d6588b5b146520a24b4acac133bb24696da3968e1fd80d07badfcffdbbe6a",
|
|
||||||
"0xa281cb84af30f3989ca6a9274866cc6ed514b733aed3fdd623d2dada24845d487f11c3d41a785aa6c78465029987d066",
|
|
||||||
"0x811a31d4ccc4276ab6c9a20ed963b04c78dfdab1cf4be9267db0ff0199502d417bbb379a392ecd4d68211e7e48233328",
|
|
||||||
"0xa4c43910dec1f8db35a2e4ae537f20f865a08bf313b614c66af4e6c682f67bfabb7c4401c01aef0cf9d7a50de034c2fb",
|
|
||||||
"0xb2e85a231753a8158106751d85b725d996c3f4e85393fa488c2fe1eb8dd1a61e605e26cdeb304cde9a67095e57d8b495",
|
|
||||||
"0x91a89b32f7ad2f4c15309befff4ed2755a4dced9c3da7dd8ca8c471888e4d3961779b6cc821c8dacedf60562ddb174bd",
|
|
||||||
"0xaeaa2cbd26d616a00f08a39427f10ce8247ecfa03a3e1660b991731e8af499466f93b91f6af3d2f848bdd768b36d9977",
|
|
||||||
"0x93d5b5ab7738e739ff410102597b157a3c075278562a46a136c42bc92be9e4dcb0d3c707a11cb7c485e90c315c8b6897",
|
|
||||||
"0x988ac3ce5b274cfd99238d073116a5ff9e43c66a21c7a98b3f6715a3a12a5fe8dfce1a3b746a2582a379d18317d4bcf8",
|
|
||||||
"0x93d23a114b4ba96c6050f59a2d0c4440020fa15e4355065e3a98d075509744412e08c4b017d5b131e7b47c7434c970f4",
|
|
||||||
"0x93f83ceeeb39768c36dd53e03211d70dca278e3b9f62a09d3e5b131aab0d3c90857c1e6fc6cefcba776a861c8fbe68cf",
|
|
||||||
"0xa38f5834bbd02d778cf0271e203d622292d60a4cb304574372c511fc40975f977b478b36ed2504b6a09aadca73195a32",
|
|
||||||
"0xae98edbc84043997e689b803ca8e1c135abf5381c874cb191d7a2cc490276bac5da4788506eb0dbdd5b7a5aa2df8c9f7",
|
|
||||||
"0x87f714197f65adc2d1ef88993c081b09f9c41f278175234d816c1f84b4e4b80161b3d67f4fd6e0eeea7c6e06847d5128",
|
|
||||||
"0xabb630876f09c6917e46c97d6e3154528ece8274b26fe9825d38e40a3290f1849d21639e28dde6b1231739d0f34efd23",
|
|
||||||
"0xb892f6b8582705faa67ae814fa4eaa5a30a097c178f53f8a0b9d58dd99b5dc914ce48dbc9172772a398464425e322bb9",
|
|
||||||
"0xb140718f955286f027074962bcf413958d66ba282f7fa45a4c15c2f6fa4b1a23e9271f47b3c8e894a66cce02279827fc",
|
|
||||||
"0x87b5c395ddda461e84d36cccd335afe71c9c5fe8ff7d9a011e56af5a4e2d5059b5a77c4c40f4582f83142e01663ecfbf",
|
|
||||||
"0xb86f0a61634c096c1547d3f6d5074c7d41493c3d5a4cf0b79b441e2ac8c10c6e2769c3737627594da32fcec25cc50d0f",
|
|
||||||
"0x8f157cbf5a2a2d1582cdf2c1a013da3aa04bf694209e2cce5c3efda6cc07999b73a57d5063358646c35711d951aa24b5",
|
|
||||||
"0xaaa675dd6ce103c6374ca148647ff4518c092ecddc5652b3e8d123a9d2d8b5ba1a878aeb275f224b50c0e6d718729501",
|
|
||||||
"0x818548e74a34e7f2b3f4e1af8f72fef5d6c96f5da596a02027d11f8d4ae93f8752d1a69bc6796b5370816f0586d5e49d",
|
|
||||||
"0x91b640fad117cd3a18e01605e12a5ec2ae81d7857529f4b1dcd98b1bc30b438f9583cf1ab7f2a3d3ec4ef342c63b10e5",
|
|
||||||
"0x8e5fe6dc7c5c220680741db6891ea41c239419a08af8daf4a4fd7d89499b1299679a32a413074181c0b83a6837e557db",
|
|
||||||
"0x98158add05884e701ea06b0365d2c5ac977b8502e570175f987ad7d5b09d7cdfd70e18003165e82ec8c53de32ceb251e",
|
|
||||||
"0x93aef40c696ecbe9058952c7abd5700aebc2144a89c720d05d99bfaedcff712d97bc0b5cda0b2aad2ed9b1b0aec0c4df",
|
|
||||||
"0x921b1c7d631771e28dc54846b8d3076c195cdfad1955b7d6c3c97befa62a02b779b65279679cc1dd89d50f0ba7afc8cb",
|
|
||||||
"0x93562f10036add83b59f62f2d7448c99dabfbd3f51c50399ea5f546c11ec8afc727b7a96732af41297978c0569140c3a",
|
|
||||||
"0xac8d6d26e3548c0622652c103361a0b2a7d6ebd7bf3cf1ba5b6d586becb1c61b166a0cbd289596b52e7997d245fb9050",
|
|
||||||
"0x8d1125dbb21e4899fc2438da5972a18e7d4ff49b1d48ff1c7e2946c0c4cd29ca7840ebdb30d3eecc80268806f58d20bc",
|
|
||||||
"0xad46ed09c5ac5807ba587edfb180984574e55b551d94d88a8db59c1b1f1656e98b74f39918957a0b9f3fc9e0f91672d7",
|
|
||||||
"0x852efa4ab9ad3114fe145b1f31031f6914240162203afddbf49cde3b1ecd29a68bc6c9a8df18aaea8b23ca4b77d8d38f",
|
|
||||||
"0x8570af9e007ab0b1d82f5958a7d8c91aa0cd006a3e43138002833cc5fbb0bd53e1db536d2ce7dcfd13a2a06e3b490946",
|
|
||||||
"0xa676181c8a6f628b907a8d60513a15d74bc51fa3d83343ab0627d8ac4a14dabb122c287d264ef2ecee03fb79bba76114",
|
|
||||||
"0x8f0c8c9cadcf34820f162ea8584b6b61dabbe16a61b0ae4faa03305c393c26796ca2106ab8a1fddf17af71b063e63524",
|
|
||||||
"0xb13a75a960eb73fc858807237666d7ad86c328cf74da947bc80cd874afc1cc3b417a90f75b0f3558f0fe30445a163333",
|
|
||||||
"0x9141877bf22764a12270f232b4c812555f7840fa1c1c67b4ca6cbe5d10ccfdebd7b0690a70dfc99d1366fc56aa5fb8cf",
|
|
||||||
"0x8c5a115c9287c550b747916787e4d671c628b0d0b688d3be91622ac66302468385429d881420232fe9eff14b84af4793",
|
|
||||||
"0x8325ed8de505dd3848ece76a2425c34f35ad4a488b77107889843dda4646be95458caa090e3a4c3c0c9cced560d57b79",
|
|
||||||
"0xb7727e8a2a246b6ab264efc8b8a851fbfe91780013f88b1d686ba6e2dd3f88099ae5cd50b087eaee4a229a209e4e11c1",
|
|
||||||
"0x874c00eae6040f8f371b3c68fed8279ba292f776f7ab0d25364c8bee33f0a865d36ef8285a92c16b84a9ceae684bc7d4",
|
|
||||||
"0xb7531fc6846e16c5950362cece96f172fa39cf8dff71e37b0ae142f74b25828cf91bff25ff91687977dc13e875942c6d",
|
|
||||||
"0x877b381025d9d3e286d40f2091514bed9a664410e58f1be36461bef1943f4fb4c63f88b1ee3115c25550868316409c4a",
|
|
||||||
"0xaca83783c5621787be754db26c5711f82f0900835a840746d01da65bc4416e5db432124bd743fee77845414974c2049e",
|
|
||||||
"0xb8185e31d16c9527f6b7311be0254f8741f58180724c4c983d2db9b2c770fbc654185b4744f5ef7f03c1b94cc2abcdad",
|
|
||||||
"0x92e2993a2632dcafd8ba986c68a1f086424dffa5f6038325c7d4effb6c2f0673f60ec57d62cdf278d809d7539b395aa9",
|
|
||||||
"0x88566790bda485f19e689bbe24dda0a977f97f199ce2633e266b9e88179a7adec98a465670ad97552913f9b5a4d32e85",
|
|
||||||
"0xa211b30b5a60935e13cbabd302216058305f8a1c3d9b53260df052590f9999c951e291dc3520510357835bcf756bd7d9",
|
|
||||||
"0x8c46615a602aa3c5f162dd0d378853b5625bc081c8da5bc68450029b53f7183431a6cc41ab4a78fc450f8d4558db80f1",
|
|
||||||
"0xa2af75fc3e8ad935405d9fc72558793107aa37d83c2f4ddfb734c747ce0afb36b217b72a1779f12cf1e647bc3d1ab543",
|
|
||||||
"0xb4606a0a3014dbd58b24859f08e1368b9e5a066d89ff2124985d9820bce141c52f07fd2bb218e963e6c05d785abf850a",
|
|
||||||
"0xa285ac44a970485c2d705fd1487d5f4b5d2fb9f1537c891d0eb5936a7d4ca50f74c7a6c660cc19a23757cbae81a2c455",
|
|
||||||
"0xb720f8dfb918aee85a09feac2ecc48ccc41545d9bf7d5a95a0ab8ddafcd627b62529fd20ed013c516b34220015fd596f",
|
|
||||||
"0x9543688acfcd61632778782c77230aa53ce964a004cb3186be5914a9a62ccaa70f1c72734a83337c89107fdb2c184b9c",
|
|
||||||
"0x91a4e2ab7f3efcccdd433415298344a3a98516f529e02f8bf9483ab9a52d3a814c17c7a6b40c87302e84242ac4b7b4d9",
|
|
||||||
"0x8eab08992fe9606efac72326ba6e03c9ac7f3dd6c9997d17346e87a0eb6bee47f94e7e3e429b2fe1e159ef38a3a917e3",
|
|
||||||
"0x97f76b38b6a5fc51c7b476a28237129d0382196efc9357f1e5590fa5217e4496710ccc9beeda38f6cf0d5f01c330909c",
|
|
||||||
"0xa076bd5dfdd2ec2ca7bba3116996210147cf9b81f4cbfe3518ee13f9b53eb762f2c2165f70e532da161b0c6ec98d01bd",
|
|
||||||
"0xa9c7c16704060e56e71a08aa2f6c6ad8b0f22272a8d64f13a4cce07c37466f45e94e963c20ab799c8230fd5bafa13b88",
|
|
||||||
"0xb6c3420776327ced738aa33ac2a4066bcb6b79970172a602125f8d5516c2e247db5ce2ece8d1961f5daf9c2d75070d5d",
|
|
||||||
"0xa80b7cd2f212be04f646f11276fb2cffb7317f9c666749dafa11507fc76819b9594ac03de562d75219dac77e86b8c5e8",
|
|
||||||
"0x8d2b8f2d0b3c067a1c17bfa4cb6fd14abcc71dccef549ed919414424fe3416a5f7980a524882ab11362478e25bf79247",
|
|
||||||
"0x81eb2c94c95572089095f5857cbc3cab4cb0e0f39d154bee873ebb921099039ca09f374a66dcf3b170db9528741d6b2b",
|
|
||||||
"0xa6a1413c8267c31e14b4ba5f39398630310dc26a3e19841eb3af16983f0b6a7005968c602c41e36eabed868b14d7e072",
|
|
||||||
"0x8eb6298d4445f3ce9c86eb34f2907d2583934c810aa94f8b91b571da3cc3056de86701ec00e6d5ba9ce90d5d56c6138f",
|
|
||||||
"0xabf9926830ece12b985435b97cb9c5e52498ddbf282cb6ea2a280542058d3834ad77ed05d512e2ca8e9e13a91d9432dd",
|
|
||||||
"0x8b55691fdd7a96a67700e9c312fc3152b524032757d6786f591c93cb3a4a620c7576d6a2bd054607ca12952fa3622724",
|
|
||||||
"0x8547b122267b6e1ab1632e4ddb2af5e273c4323471efcdacf16a77f1c5a97817ce0d59a686842d6d45b5e350759b37d4",
|
|
||||||
"0xa0c3b915a9b4e9c20a55354a7d62e4cbeba0010aeb2326bf9365cbccf1b8b3d18c6eaac6c793daaf5cce15153af93483",
|
|
||||||
"0x8c770c76f380dc1bfe9e79cf7f60e9868af37300f30528380ee6b6f161e7ce09a8a1b5a5dd23903cc551a52c43fad6d5",
|
|
||||||
"0xb233d0349242b965e6c69c4d286fe12c378ffb3ddd8e08938098298a0b23c59e0686d2a190b28a13d86b5ff618476959",
|
|
||||||
"0xaade99bd0c2923fe2df52bb8dac4321387e51f3256eba99a7f0645b0aaf55af4885d158eb427d9e84d450002254bd262",
|
|
||||||
"0x95050a292a0bb69c866d92bf1095581ad2aa94ed585b5760f794d4de947e313f05171025c2a85b5a825aace731933c19",
|
|
||||||
"0x803eb6752b16d33d92c84e2abdbf1056fd2f89df51e00734fc33ca6f9b2a2bdc103e6bc368cca32a6616cdb600284c3d",
|
|
||||||
"0xae93b201d1dfc90ec61cfc379ff1eab01558308a22a77a32765eb8cc27467fb86fbf2339011f525de26a4e37a9d4d060",
|
|
||||||
"0x8a4c740d4a881d4dc0514a212f8cf88d96c711cf660956566e1026eeae96b25c42ad7474046ab08115c2353a125841be",
|
|
||||||
"0x8afa88a77d7e847ce9b13201b19764caf07c79666f150122b133bf29eaeea07a2c5d7d868bf9a983d426ef5977efd7fb",
|
|
||||||
"0x95f586105b14e1bab698e18f94a0bf2cf223f092271d28c9f6e72c3de88d6232527d85906190b77f953ab4289ca2ce39",
|
|
||||||
"0xa74adf3914fdc3717beb02a98ae0223d0c6e093220a43504ff73c9fb0225bbf145a718d78dd448015143fed8bea66d2a",
|
|
||||||
"0xb1a6f5883ef2afb4bcde5cc018fd29e56d4d7e65a9e829d7f5f07d3d210deceb48dd4972dbf740334ccc99b61e46eba6",
|
|
||||||
"0x851aa8cfe9538ccfa863700af3fe7395bdd3d051eff006e86053e912d1d60fc1bcd87aee23eeb8ecb3f547b17dfd53ec",
|
|
||||||
"0xac4aa49cdafb0847f1f7a9bc2a44af01ee8696fe189f618b751dce4cdf9f041e9a1e34d3c096317483f432df7799db33",
|
|
||||||
"0xa274d9ce5fe87a0436b1d6b606db605c1fb27594aab1af81c2e185b2653ee15fe1526c5e6177a44fcf9cedbb2d4793b8",
|
|
||||||
"0xb971066e3bed163cfa364fda1e11dc7c8176eb6721d98937d29d5103c3cac1dcba1d610e54bac9e74341fb3d4c5961ab",
|
|
||||||
"0xb6c74d8a586690b97d10bc4b99b2e9acc1dcb1d58634a327729d699fef68da9cfdca7b408fd74efc267eaed553257024",
|
|
||||||
"0xb827864273932536c3135ed80e9378e27e6d21d73157ec24ec31bdca4ab6457be1dd4830671ba6429a74aed3c02fec32",
|
|
||||||
"0x949894777c68920cdc9de840ff2748633964458e4b9dbbe18b21bba5a01d3f0357611d3c59b2fe5509a4245fdff89f84",
|
|
||||||
"0xb9bb52f7147392b8f0d1662825c64e1caabaacf11b7499c3fe6f9d6f86a3a51548a75ba8e9e5fc5aa656c8a34aaa6b9b",
|
|
||||||
"0xa32b694ffc3ff7005e6aac7a988950e4f2da29b567e38acc73207f95ba3f7b3594de1537898cd9b4d1f5907211858f26",
|
|
||||||
"0x849ba686af1f6054de495636a2301805c8c01da3470592d2ad3d18d678de22ae332c8dca6b8420e10f42361beb5baf90",
|
|
||||||
"0xb99832e7f0cc9e018bacad7c1196c1be70b6abd81e389d977839ce6662ffc981f10657ebf9ac30e23d7a27363eec4077",
|
|
||||||
"0x99c6e19e57d69e577be61c6266f81cde376b9f35abb0184a2686882e4476967439862b9a530cae0ead29b741d2eb172e",
|
|
||||||
"0x942de4b667daa85d48e352b051ee29c8f3752b90ee92863f854866f21b99c97babeabe972224b4d84db621ca782ebf4f",
|
|
||||||
"0x85d94096a35da022155562bf5d21f99228062ca203888975d59d2b3e91fa4dff6dda6930f304f51f9f7aa54679b82e6a",
|
|
||||||
"0x822544b8a8eeb609f2837123433d0170ea512a16b2b10db245102f6026599a042ffc521efb92eb141a1d2a4214bb6917",
|
|
||||||
"0xa62e2cdc1486f555f0f58a3b4abe8576388e8d6fa650656b0d4fbf75510768a9f45a889d9171a2deec3fd98f50eca558",
|
|
||||||
"0x833b9040774febef4caa97b47a825cab0d1150da5304be94bb7e5fb36f2ce5918bb46106a987f0d8d8b4e35c86f6e308",
|
|
||||||
"0x85ca18e6f8c9f5e0d4e8c2d203395be3a17b941b15cbf911e581f99a4bac640c399daa180820c1afb1f591e5416a476b",
|
|
||||||
"0x837649d4fa89066ec02b4ea6bc0839a3394b26c38e2a46e75dc2da15ba8be23e9a7f9f66015b549beefb325b5c77f1c8",
|
|
||||||
"0xa2d82f55ed9a3e31133708dd5aa27a37d12d5df7ab2f00455a3846de7562d27a5d06a7735dd506edf11cb10e93fec320",
|
|
||||||
"0x8e5e3ad45313da6bffeb52325561188611d23ce9e38c781757b591987cad3b14f5b8b6889cbd550888128619fe3f1b5e",
|
|
||||||
"0x8d037d57cfe814d91dd8f44431ebfebe16382402246a89303776fae8c746709fc069b0825227db0a15b5cd75195db910",
|
|
||||||
"0x940c9c5f145d64f1ff2dbe20c73361f8031aa5c28243d4740e0feea66c3a9922e63596f49d5ee6184a58ae92ed126a2c",
|
|
||||||
"0x9165f88c51f25491e3c48b5414758ba124f4d43741977665e1d910b29e2df84f0a615ce143504e7353ab0b47c46e1646",
|
|
||||||
"0xb1f39e3644cc1ed40328fc0098dd4064a258722cf65e80dbd9cd9ebe0f11cab31842fd9483be201627e3660500ff2c23",
|
|
||||||
"0xb5e47f405760322b46d37f63955bea7555f6065ac17a580d2eda371309d48759aa462bfd4067aac6730ea6391a9bdb0a",
|
|
||||||
"0xb949285d17dd61438eea643d1ff0f85463b552d98ccb4a65c9439d84e43ee1db49f3af027e5febb56e1f6f5d80c91412",
|
|
||||||
"0x8b6ae62bb33cd0966d7fd59d6b7f92c7c258c8c718eda58bc0b9ab61e60ca94c82af267d7a2857f694f0c517c7593a6f",
|
|
||||||
"0xa8709ee9482be378884a40098e5066c9e66beafb454505a7a300f38f102d61ed70d5ae00e9c248b9840cc45139d0a446",
|
|
||||||
"0xa5cbb5daaaf6fc5d5a9caf82c88666f33bbeb695997eac016995c687bdf91cbad2b9f32213bbe2c9bc558a4717892056",
|
|
||||||
"0x979c292e65c2fab5639a5d684dc1529888bd60f0aee97dec899725bfc9e22a98fd8374d022d366e10e2f0a3d4c3e6b66",
|
|
||||||
"0x82c0597dd33bc8e9b3b3e66a0dfe80b3058003d733518255471ce634dcdbd26b12dfc2b7ed7062181a84fd939261ecb3",
|
|
||||||
"0x95e1c90de7bede21c3270301f6fb9ad6ee18c86604a68999d2421be3b09b66299775cabe49c83658b1c34ab132a240ba",
|
|
||||||
"0xb6c3e5fe6262b8bd3472516d05fa170993dfbc24adb4aa5dffb8b387cad1a4024850400b27be8ad7a88c6ded0cfb40b5",
|
|
||||||
"0xb652d5448b08a9acb8820583d69037d6eac4538bf01100aa2ccc0a35d0e741511047edcacd9e1fbbbf144dc7a68e3a62",
|
|
||||||
"0xad26a763e5b9bfcf1e9da160227ae3184ceb0ca160e497ed955eb5474cb1fb15222ebd2b95bf5f79df34b708826b5135",
|
|
||||||
"0x80bd2a8cc1238f91209cb483297e3fcbb7aa6baf3124a75d98da96a075107c0cbe4cbed9fe8bd901fc5c9583e68f8318",
|
|
||||||
"0x948f28741b3e4cf9eb2f1e14620c011030b6d38ed2cd0ee9fe41ca5bd54529cac5c7b7f581212878ed48f30a298c05cf",
|
|
||||||
"0xa34fbf6ef71e8bafe5fb98e7ee99de5fc60f6dd8f2b15407d052158d0f34a7ebce63490ec71f4da534816840be83cfbd",
|
|
||||||
"0xaaf6740dc86b6c4501a19068e190d44b9d07af8e2a3801875db6e3d183c10a86c61e0d362d3c11bfc73a1f8c9a55183b",
|
|
||||||
"0xaee2373d7868128c28bb58637d85c148f1aed881d8157090ed998af6d961bf1dc37391294b0e361f5578d3fcb5bd60ca",
|
|
||||||
"0x842edd482f9bddf87300b2d1c916f141340691ccb6b36fdd396f50cf79fb856d793bd50c8eafef5c949faa33d57ad54b",
|
|
||||||
"0x8fb98a9e82933843b995ec0d929fb775c27efe51f01e9fe3d4abf11b03281cb6df26c3b844102902097397ba8a4ed95b",
|
|
||||||
"0x890d76989026e2ec0ffb92eeec65a552ec7671c17cdad6c22fed7aa7752a9531304239661ea07ce12b93404d1d556127",
|
|
||||||
"0x98bf7919c81f8b14e6ebda09844bb2609860669a28347a13c303bc62893f62762159fb8009ad9a8fd4d320ee4d2b3cfe",
|
|
||||||
"0xa69571f3cd933820ba3b81c20630a75a49aa6e9a019cf9815da31efab1d906fa0d02dc65b3726460de6f8dd001ca8526",
|
|
||||||
"0xa21e6285c2dd8ab627e9052440c859e59937d83d2ba9b85f65ab7f9249c4a2de746b3feaeee4350d478fcdf0421422b6",
|
|
||||||
"0x90273ef6426d6b128763e367507615e6a79220aba9c30d8a194c5da350c065bfd378c061b6e3e57ca5dad6e430e9c6a7",
|
|
||||||
"0x89df93db35de1404230f5af7561385929220d5cbeaca1559bc5f3097458477b20739847b707e52e5cf31edba92529a21",
|
|
||||||
"0xb3fe1edd1d8dce5efb068dce704e073a3473b2f78d04b59e43d3b96d58778d4fa00d4f08f60a267c14e61f8f629f7205",
|
|
||||||
"0x881a126490d54dfedbb4bd1e951f8cdfeafff7b164a1b4786ec577cdb7c9a75b29e2c65ca1749e0417fd9cd2cc3a3cb1",
|
|
||||||
"0x810646bb3b98b0fa7d4541ce5cebf5526e501807455aaaf5c0e7a393f832a93e61b701b5e14c713ce519979085aca5fe",
|
|
||||||
"0xa27e64f8d9cad96f156bee43b5002970f034149ed74833a3a85cf5ff7e0c73fbd7704c696db98cf79c80e37c1d091d59",
|
|
||||||
"0xa78dcd5e2b7934fef51673c465419e2c057399f792e36002ef5aee09d2efa0845b4bc8e001bd70ba957a567d233159e4",
|
|
||||||
"0xad4ecb76e8a30e427af08dfc7fdd37d3b69ce3f41dc374d28bbc1b4be3b82984a3c32b9792fbd20333a5305a7cdd23b4",
|
|
||||||
"0x85c01bdf47745c94c1e93e92575133c757a419c91546d681902d50b94dde97f7d3fc0323337f791283762026e0ba4b17",
|
|
||||||
"0xb9cef0b3c0055475dfa4f8bc9c30ba514be1caca38275a1215215c8731aa21959c7097582002f8fabb8f3bf54623489e",
|
|
||||||
"0xa96e8620be447aeb3eeba0277da17537206f23ffcde12ec63fef45d741bb28a973ee48bf5be317caf7a2d172e38ab550",
|
|
||||||
"0x8dae5a64d0a980ec81caea6ae9971f0723feb042d30e8aa6a109a1ed8ae5689b05792d48b1ed54d95388fdc618e25505",
|
|
||||||
"0x96130e73fb0cb8e56d11e2939c977999cdbf01bc0598b228d7485067b55d9b9ec896f0490afa2ae6699b802585b0b00d",
|
|
||||||
"0x85bb03bd0cca1030979a01ffa2dc8178bd1add6c739179f950e21bbd576b10f3dd41de5148f3d4eaf0f6019bacfc19de",
|
|
||||||
"0x8155b105b3f0d6198cf80c62aae83185182a717189c00c6180a6b620a5c094b993324201c4638d65a95e04210b41dcb2",
|
|
||||||
"0x853ccde301148f3e5aff12409a29f87deaf319bbd0245b0a20f10c117227ab33a5aa5ae81447aca9c0dc708ea7d809ec",
|
|
||||||
"0x909b5a6b09ffd5a64ac1cdce0143d09833a5c9bf5c5d190995dd37e5945c2c89d3aa5d1cd7cb5a46318752c8b6832152",
|
|
||||||
"0x94c5d61e616b16eabffdc6fe35e9bae55d8da01580ccb15a18802502d1872484b386b50893801ee8c15c8ac7ed7ed176",
|
|
||||||
"0xa49feb98f5e8872446e5f8e919d20b7bbb82f2fabe59458f416deb4fe2f415d579bd7ac45b286d522557c53e7df2b82b",
|
|
||||||
"0x9071145e343177cfb81dc5e68648309b8b61636d30eb0ab6624bae58e8992d98951c299665a4139d12d4ea2f505ce265",
|
|
||||||
"0xa68a488b0aaa3b060c0778a6e87e4f2dff7c806abf0a2125f96bbf3f0ad191d32fcf87d27cfa06eca4a9618eb48b2bee",
|
|
||||||
"0x991febb335fc4eb22cd341abca8320fefa132e394d115cc8d6ca5b0d5de5487d458c5165a0b504bd85ea8c842a8dbf29",
|
|
||||||
"0xa12bcbdb57fb851f0fa9ea0e1caf0929e8bef55ac71bb236c70124abae06581bff34a2b93d8a063e2bd42711ff535316",
|
|
||||||
"0xa6a0ee28bf7027849b5605965c14ee75db82dc5d9374a8fe568fef6123719ca1698bed51f74fe872e6075c6ae0d3c8cb",
|
|
||||||
"0x8559c5305305b10adee0b87536e18b3e70978b4f458531aacf3af132fffa68f7111c5155fdb1e5aedec27b746a99dff2",
|
|
||||||
"0xa6a5d24dd7822c51f5b2fef526c735733a2eb5ab9ee6848f4e0777367bb2fd966e830056a54943f9ad9ee712dde5866a",
|
|
||||||
"0x901b638eea2e8e7b1405ca19b80ea9be8d84fd117b1f8c22bd176a918fd5093d38eb720a423df577b8f219ed2fd994e9",
|
|
||||||
"0xb5810330244b12529cb2596b4a3ff31305f8aab243bfba01da770b5754ee2d73ff7c9aa657a9e0cd0900410207c23644",
|
|
||||||
"0x8451719fd8c98232f9b9cbb1ded46b1caff52b3d7509533016621fa49c461f129c2adbcd4941a500fd5209c9196acaf4",
|
|
||||||
"0xa9749b942997de005582043d60ceeeaa6ac945c688ee3258e46ecd927690cd15412f827a6bd43b42c26f3d887415ff35",
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
|
@ -1,4 +0,0 @@
|
||||||
import Client from "./client.js";
|
|
||||||
import Prover from "./prover.js";
|
|
||||||
|
|
||||||
export { Client, Prover };
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { AsyncOrSync } from "ts-essentials";
|
|
||||||
import { LightClientUpdate } from "./types.js";
|
|
||||||
|
|
||||||
export interface IProver {
|
|
||||||
getSyncUpdate(
|
|
||||||
period: number,
|
|
||||||
currentPeriod: number,
|
|
||||||
cacheCount: number,
|
|
||||||
): AsyncOrSync<LightClientUpdate>;
|
|
||||||
}
|
|
||||||
export interface IStore {
|
|
||||||
addUpdate(period: number, update: LightClientUpdate): AsyncOrSync<void>;
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
import { digest } from "@chainsafe/as-sha256";
|
|
||||||
import { IStore } from "./interfaces.js";
|
|
||||||
import { LightClientUpdate } from "./types.js";
|
|
||||||
import { LightClientUpdateSSZ, CommitteeSSZ, HashesSSZ } from "./ssz.js";
|
|
||||||
import { concatUint8Array } from "./utils.js";
|
|
||||||
|
|
||||||
export class MemoryStore implements IStore {
|
|
||||||
store: {
|
|
||||||
[period: number]: {
|
|
||||||
update: Uint8Array;
|
|
||||||
nextCommittee: Uint8Array;
|
|
||||||
nextCommitteeHash: Uint8Array;
|
|
||||||
};
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
addUpdate(period: number, update: LightClientUpdate) {
|
|
||||||
this.store[period] = {
|
|
||||||
update: LightClientUpdateSSZ.serialize(update),
|
|
||||||
nextCommittee: CommitteeSSZ.serialize(update.nextSyncCommittee.pubkeys),
|
|
||||||
nextCommitteeHash: digest(
|
|
||||||
concatUint8Array(update.nextSyncCommittee.pubkeys),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getUpdate(period: number): Uint8Array {
|
|
||||||
if (period in this.store) return this.store[period].update;
|
|
||||||
throw new Error(`update unavailable for period ${period}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCommittee(period: number): Uint8Array {
|
|
||||||
if (period < 1)
|
|
||||||
throw new Error("committee not unavailable for period less than 1");
|
|
||||||
const predPeriod = period - 1;
|
|
||||||
if (predPeriod in this.store) return this.store[predPeriod].nextCommittee;
|
|
||||||
throw new Error(`committee unavailable for period ${predPeriod}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCommitteeHashes(period: number, count: number): Uint8Array {
|
|
||||||
if (period < 1)
|
|
||||||
throw new Error("committee not unavailable for period less than 1");
|
|
||||||
const predPeriod = period - 1;
|
|
||||||
|
|
||||||
const hashes = new Array(count).fill(0).map((_, i) => {
|
|
||||||
const p = predPeriod + i;
|
|
||||||
if (p in this.store) return this.store[p].nextCommitteeHash;
|
|
||||||
throw new Error(`committee unavailable for period ${p}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return HashesSSZ.serialize(hashes);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
import * as altair from "@lodestar/types/altair";
|
|
||||||
import { IProver } from "./interfaces.js";
|
|
||||||
import { LightClientUpdate } from "./types.js";
|
|
||||||
import { handleGETRequest } from "./utils.js";
|
|
||||||
|
|
||||||
export default class Prover implements IProver {
|
|
||||||
cachedSyncUpdate: Map<number, LightClientUpdate> = new Map();
|
|
||||||
private serverUrl: string;
|
|
||||||
|
|
||||||
constructor(serverUrl: string) {
|
|
||||||
this.serverUrl = serverUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getSyncUpdates(
|
|
||||||
startPeriod: number,
|
|
||||||
maxCount: number,
|
|
||||||
): Promise<LightClientUpdate[]> {
|
|
||||||
const res = await handleGETRequest(
|
|
||||||
`${this.serverUrl}/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)!;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
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";
|
|
|
@ -1,13 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
export { VerifyingProvider } from "./provider.js";
|
|
|
@ -1,711 +0,0 @@
|
||||||
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.js";
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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(
|
|
||||||
providerURL: string,
|
|
||||||
blockNumber: bigint | number,
|
|
||||||
blockHash: Bytes32,
|
|
||||||
chain: bigint | Chain = Chain.Mainnet,
|
|
||||||
) {
|
|
||||||
this._rpc = new RPC({ URL: providerURL });
|
|
||||||
this.common = new Common({
|
|
||||||
chain,
|
|
||||||
});
|
|
||||||
const _blockNumber = BigInt(blockNumber);
|
|
||||||
this.latestBlockNumber = _blockNumber;
|
|
||||||
this.blockHashes[bigIntToHex(_blockNumber)] = blockHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _rpc: RPC;
|
|
||||||
|
|
||||||
get rpc(): RPC {
|
|
||||||
return this._rpc;
|
|
||||||
}
|
|
||||||
|
|
||||||
async 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.getBlockHeader("latest");
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
public rpcMethodSupported(method: string): boolean {
|
|
||||||
return this._methods.has(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
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: any[] = 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)];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
import NodeCache from "node-cache";
|
|
||||||
import { PluginAPI } from "@lumeweb/interface-relay";
|
|
||||||
import stringify from "json-stringify-deterministic";
|
|
||||||
|
|
||||||
export type RPCRequest = {
|
|
||||||
method: string;
|
|
||||||
params: any[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RPCRequestRaw = RPCRequest & {
|
|
||||||
jsonrpc: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RPCResponse = {
|
|
||||||
success: boolean;
|
|
||||||
result: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ProviderConfig = {
|
|
||||||
URL: string;
|
|
||||||
unsupportedMethods?: string[];
|
|
||||||
supportBatchRequests?: boolean;
|
|
||||||
batchSize?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class RPC {
|
|
||||||
get cache(): NodeCache {
|
|
||||||
return this._cache;
|
|
||||||
}
|
|
||||||
private provider: ProviderConfig;
|
|
||||||
private _cache = new NodeCache({ stdTTL: 60 * 60 * 12 });
|
|
||||||
|
|
||||||
constructor(provider: ProviderConfig) {
|
|
||||||
this.provider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _pluginApi?: PluginAPI;
|
|
||||||
|
|
||||||
set pluginApi(value: PluginAPI) {
|
|
||||||
this._pluginApi = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async request(request: RPCRequest) {
|
|
||||||
if (this.provider.unsupportedMethods?.includes(request.method)) {
|
|
||||||
throw new Error("method not supported by the provider");
|
|
||||||
}
|
|
||||||
return await this._retryRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestBatch(requests: RPCRequest[]) {
|
|
||||||
if (
|
|
||||||
this.provider?.unsupportedMethods &&
|
|
||||||
requests.some((r) => this.provider.unsupportedMethods!.includes(r.method))
|
|
||||||
) {
|
|
||||||
throw new Error("method not supported by the provider");
|
|
||||||
}
|
|
||||||
|
|
||||||
const res: RPCResponse[] = [];
|
|
||||||
for (const request of requests) {
|
|
||||||
const r = await this._retryRequest(request);
|
|
||||||
res.push(r);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _request(request: RPCRequestRaw): Promise<RPCResponse> {
|
|
||||||
try {
|
|
||||||
const response = await (
|
|
||||||
await fetch(this.provider.URL, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
})
|
|
||||||
).json();
|
|
||||||
|
|
||||||
if (response.result) {
|
|
||||||
const tempRequest = {
|
|
||||||
method: request.method,
|
|
||||||
params: request.params,
|
|
||||||
};
|
|
||||||
const hash = (this._pluginApi as PluginAPI).util.crypto
|
|
||||||
.createHash(stringify(tempRequest))
|
|
||||||
.toString("hex");
|
|
||||||
// this._cache.set(hash, response);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
success: !response.error,
|
|
||||||
result: response.error || response.result,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
result: { message: `request failed: ${e}` },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* public getCachedRequest(request: RPCRequest): RPCResponse | null {
|
|
||||||
const hash = this.hashRequest(request);
|
|
||||||
|
|
||||||
if (this.cache.has(hash)) {
|
|
||||||
this.cache.ttl(hash);
|
|
||||||
return this.cache.get(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public deleteCachedRequest(request: RPCRequest): void {
|
|
||||||
const hash = this.hashRequest(request);
|
|
||||||
|
|
||||||
this.cache.del(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private hashRequest(request: RPCRequest): string {
|
|
||||||
const tempRequest = {
|
|
||||||
method: request.method,
|
|
||||||
params: request.params,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (this._pluginApi as PluginAPI).util.crypto
|
|
||||||
.createHash(stringify(tempRequest))
|
|
||||||
.toString("hex");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
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.)
|
|
||||||
};
|
|
|
@ -1,68 +0,0 @@
|
||||||
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),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
import { InvalidParamsError } from "./errors.js";
|
|
||||||
|
|
||||||
// Most of the validations are taken from:
|
|
||||||
// https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/client/lib/rpc/validation.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,23 +0,0 @@
|
||||||
import {
|
|
||||||
ByteVectorType,
|
|
||||||
ListCompositeType,
|
|
||||||
VectorCompositeType,
|
|
||||||
} from "@chainsafe/ssz";
|
|
||||||
import * as capella from "@lodestar/types/capella";
|
|
||||||
import { BEACON_SYNC_COMMITTEE_SIZE } from "./constants.js";
|
|
||||||
|
|
||||||
const MAX_BATCHSIZE = 10000;
|
|
||||||
|
|
||||||
export const LightClientUpdateSSZ = capella.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);
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { BeaconConfig } from "@lodestar/config";
|
|
||||||
import * as capella from "@lodestar/types/capella";
|
|
||||||
|
|
||||||
export type PubKeyString = string;
|
|
||||||
export type Slot = number;
|
|
||||||
export type Bytes32 = string;
|
|
||||||
|
|
||||||
export type OptimisticUpdate = capella.LightClientOptimisticUpdate;
|
|
||||||
export type LightClientUpdate = capella.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 };
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { fromHexString } from "@chainsafe/ssz";
|
|
||||||
import { createBeaconConfig } from "@lodestar/config";
|
|
||||||
import { mainnetConfig } from "./constants.js";
|
|
||||||
import { networksChainConfig } from "@lodestar/config/networks";
|
|
||||||
import fetch from "node-fetch";
|
|
||||||
|
|
||||||
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 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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleGETRequest(
|
|
||||||
url: string,
|
|
||||||
retry: number = 3,
|
|
||||||
): Promise<any> {
|
|
||||||
if (retry < 0) {
|
|
||||||
throw Error(`GET request failed: ${url}`);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const ret = await fetch(url);
|
|
||||||
return await ret.json();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`failed GET request (${url}): ${e.message}`);
|
|
||||||
return handleGETRequest(url, retry - 1);
|
|
||||||
}
|
|
||||||
}
|
|
170
src/index.ts
170
src/index.ts
|
@ -1,10 +1,12 @@
|
||||||
import type { Plugin, PluginAPI } from "@lumeweb/interface-relay";
|
import type { Plugin, PluginAPI } from "@lumeweb/interface-relay";
|
||||||
import { Client, Prover } from "./client/index.js";
|
import {
|
||||||
import { MemoryStore } from "./client/memory-store.js";
|
ConsensusCommitteeHashesRequest,
|
||||||
import { computeSyncPeriodAtSlot } from "@lodestar/light-client/utils";
|
ConsensusCommitteePeriodRequest,
|
||||||
import { toHexString } from "@chainsafe/ssz";
|
createDefaultClient,
|
||||||
import { DEFAULT_BATCH_SIZE } from "./client/constants.js";
|
getConsensusOptimisticUpdate,
|
||||||
import { handleGETRequest } from "./client/utils.js";
|
} from "@lumeweb/libethsync/node";
|
||||||
|
import { RPCRequestRaw } from "@lumeweb/libethsync/client";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const EXECUTION_RPC_URL =
|
const EXECUTION_RPC_URL =
|
||||||
"https://solemn-small-frost.discover.quiknode.pro/dbbe3dc75a8b828611df3f12722de5cc88214947/";
|
"https://solemn-small-frost.discover.quiknode.pro/dbbe3dc75a8b828611df3f12722de5cc88214947/";
|
||||||
|
@ -16,70 +18,40 @@ interface ExecutionRequest {
|
||||||
params: any[];
|
params: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConsensusCommitteeHashesRequest {
|
const executionClient = axios.create({ baseURL: EXECUTION_RPC_URL });
|
||||||
start: number;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ConsensusCommitteePeriodRequest {
|
|
||||||
period: number | "latest";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ConsensusBlockRequest {
|
|
||||||
block: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
let client: Client;
|
|
||||||
|
|
||||||
const RPC_NO_CACHE = [
|
|
||||||
"eth_call",
|
|
||||||
"eth_estimateGas",
|
|
||||||
"eth_sendRawTransaction",
|
|
||||||
"eth_getTransactionReceipt",
|
|
||||||
"eth_getTransactionCount",
|
|
||||||
"eth_getProof",
|
|
||||||
];
|
|
||||||
|
|
||||||
const plugin: Plugin = {
|
const plugin: Plugin = {
|
||||||
name: "eth",
|
name: "eth",
|
||||||
async plugin(api: PluginAPI): Promise<void> {
|
async plugin(api: PluginAPI): Promise<void> {
|
||||||
const prover = new Prover(CONSENSUS_RPC_URL);
|
const client = createDefaultClient(CONSENSUS_RPC_URL);
|
||||||
const store = new MemoryStore();
|
|
||||||
client = new Client(prover, store, CONSENSUS_RPC_URL, EXECUTION_RPC_URL);
|
|
||||||
await client.sync();
|
await client.sync();
|
||||||
client.provider.rpc.pluginApi = api;
|
|
||||||
const provider = client.provider;
|
|
||||||
|
|
||||||
api.registerMethod("consensus_committee_hashes", {
|
api.registerMethod("consensus_committee_hashes", {
|
||||||
cacheable: false,
|
cacheable: false,
|
||||||
async handler(
|
async handler(
|
||||||
request: ConsensusCommitteeHashesRequest,
|
request: ConsensusCommitteeHashesRequest,
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
if (!(request?.start && typeof request.start == "number")) {
|
if (!(request?.start && typeof request.start === "number")) {
|
||||||
throw new Error('start required and must be a number"');
|
throw new Error('start required and must be a number"');
|
||||||
}
|
}
|
||||||
if (!(request?.count && typeof request.count == "number")) {
|
if (!(request?.count && typeof request.count === "number")) {
|
||||||
throw new Error('count required and must be a number"');
|
throw new Error('count required and must be a number"');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client.isSynced) {
|
let hashes: Uint8Array = new Uint8Array();
|
||||||
await client.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
let hashes;
|
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
try {
|
try {
|
||||||
hashes = store.getCommitteeHashes(request.start, request.count);
|
hashes = client.store.getCommitteeHashes(
|
||||||
} catch {
|
request.start,
|
||||||
await client.sync();
|
request.count,
|
||||||
}
|
);
|
||||||
|
|
||||||
if (!hashes) {
|
|
||||||
try {
|
|
||||||
hashes = store.getCommitteeHashes(request.start, request.count);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (i === 3) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
await client.sync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashes;
|
return hashes;
|
||||||
|
@ -106,24 +78,27 @@ const plugin: Plugin = {
|
||||||
|
|
||||||
let committee;
|
let committee;
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
try {
|
try {
|
||||||
committee = store.getCommittee(
|
committee = client.store.getCommittee(
|
||||||
request.period === "latest" ? client.latestPeriod : request.period,
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
await client.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!committee) {
|
|
||||||
try {
|
|
||||||
committee = store.getCommittee(
|
|
||||||
request.period === "latest"
|
request.period === "latest"
|
||||||
? client.latestPeriod
|
? client.latestPeriod
|
||||||
: request.period,
|
: request.period,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (i === 3) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
await client.sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
committee = client.store.getCommittee(
|
||||||
|
request.period === "latest" ? client.latestPeriod : request.period,
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
await client.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
return committee;
|
return committee;
|
||||||
|
@ -132,26 +107,8 @@ const plugin: Plugin = {
|
||||||
|
|
||||||
api.registerMethod("execution_request", {
|
api.registerMethod("execution_request", {
|
||||||
cacheable: false,
|
cacheable: false,
|
||||||
async handler(request: ExecutionRequest): Promise<object> {
|
async handler(request: RPCRequestRaw): Promise<object> {
|
||||||
/* const cache = provider.rpc.getCachedRequest(request);
|
const ret = (await executionClient.post<any>("/", request)).data;
|
||||||
|
|
||||||
if (cache) {
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (provider.rpcMethodSupported(request.method)) {
|
|
||||||
await provider.rpcMethod(request.method, request.params);
|
|
||||||
} else {
|
|
||||||
await provider.rpc.request(request);
|
|
||||||
}
|
|
||||||
let ret = provider.rpc.getCachedRequest(request);
|
|
||||||
|
|
||||||
if (RPC_NO_CACHE.includes(request.method)) {
|
|
||||||
provider.rpc.deleteCachedRequest(request);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
let ret = await provider.rpc.request(request);
|
|
||||||
// @ts-ignore
|
|
||||||
return { ...ret, id: request.id ?? ret.id };
|
return { ...ret, id: request.id ?? ret.id };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -159,60 +116,7 @@ const plugin: Plugin = {
|
||||||
api.registerMethod("consensus_optimistic_update", {
|
api.registerMethod("consensus_optimistic_update", {
|
||||||
cacheable: false,
|
cacheable: false,
|
||||||
async handler(): Promise<object> {
|
async handler(): Promise<object> {
|
||||||
return await handleGETRequest(
|
return getConsensusOptimisticUpdate();
|
||||||
`${CONSENSUS_RPC_URL}/eth/v1/beacon/light_client/optimistic_update`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
api.registerMethod("consensus_block", {
|
|
||||||
cacheable: false,
|
|
||||||
async handler(request: ConsensusBlockRequest): Promise<object> {
|
|
||||||
try {
|
|
||||||
BigInt(request?.block);
|
|
||||||
} catch {
|
|
||||||
throw new Error("block is required and must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
const block = request?.block;
|
|
||||||
|
|
||||||
if (
|
|
||||||
BigInt(block) > BigInt(client.latestPeriod) ||
|
|
||||||
!client.blockHashCache.has(request.block)
|
|
||||||
) {
|
|
||||||
await client.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!client.blockHashCache.has(request.block) &&
|
|
||||||
!client.blockCache.has(request.block)
|
|
||||||
) {
|
|
||||||
let state;
|
|
||||||
try {
|
|
||||||
const period = computeSyncPeriodAtSlot(request.block);
|
|
||||||
state = await prover.getSyncUpdate(
|
|
||||||
period,
|
|
||||||
period,
|
|
||||||
DEFAULT_BATCH_SIZE,
|
|
||||||
);
|
|
||||||
await client.getExecutionFromBlockRoot(
|
|
||||||
request.block as any,
|
|
||||||
toHexString(state.attestedHeader.beacon.bodyRoot),
|
|
||||||
);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.blockCache.has(request.block)) {
|
|
||||||
client.blockCache.ttl(request.block);
|
|
||||||
|
|
||||||
return client.blockCache.get(request.block) as object;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ret = await handleGETRequest(
|
|
||||||
`${CONSENSUS_RPC_URL}/eth/v2/beacon/blocks/${request.block}`,
|
|
||||||
);
|
|
||||||
client.blockCache.set(request.block, ret);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue