Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add support for web3.js to simulation tests #6641

Merged
merged 7 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import {EL_GENESIS_ACCOUNT} from "../constants.js";
import {AssertionMatch, AssertionResult, NodePair, SimulationAssertion} from "../interfaces.js";

function hexToBigInt(num: string): bigint {
return num.startsWith("0x") ? BigInt(num) : BigInt(`0x${num}`);
}

function bigIntToHex(num: bigint): string {
return `0x${num.toString(16)}`;
}

const transactionAmount = BigInt(2441406250);

export function createAccountBalanceAssertion({
Expand All @@ -30,17 +22,12 @@ export function createAccountBalanceAssertion({
return AssertionMatch.None;
},
async capture({node}) {
await node.execution.provider?.getRpc().fetch({
method: "eth_sendTransaction",
params: [
{
to: address,
from: EL_GENESIS_ACCOUNT,
gas: "0x76c0",
gasPrice: "0x9184e72a000",
value: bigIntToHex(transactionAmount),
},
],
await node.execution.provider?.eth.sendTransaction({
to: address,
from: EL_GENESIS_ACCOUNT,
gas: "0x76c0",
gasPrice: "0x9184e72a000",
value: transactionAmount,
});

// Capture the value transferred to account
Expand All @@ -57,10 +44,7 @@ export function createAccountBalanceAssertion({
expectedBalanceAtCurrentSlot += BigInt(store[captureSlot]);
}

const balance = hexToBigInt(
(await node.execution.provider?.getRpc().fetch({method: "eth_getBalance", params: [address, "latest"]})) ??
"0x0"
);
const balance = await node.execution.provider?.eth.getBalance(address, "latest");

if (balance !== expectedBalanceAtCurrentSlot) {
errors.push(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export function createExecutionHeadAssertion({
return AssertionMatch.None;
},
async capture({node}) {
const blockNumber = await node.execution.provider?.getBlockNumber();
const blockNumber = await node.execution.provider?.eth.getBlockNumber();
if (blockNumber == null) throw new Error("Execution provider not available");
const executionHeadBlock = await node.execution.provider?.getBlockByNumber(blockNumber);
const executionHeadBlock = await node.execution.provider?.eth.getBlock(blockNumber);

const consensusHead = await node.beacon.api.beacon.getBlockV2("head");
ApiError.assert(consensusHead);
Expand Down
12 changes: 4 additions & 8 deletions packages/cli/test/utils/simulation/clients/execution/geth.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {writeFile} from "node:fs/promises";
import path from "node:path";
import got from "got";
import {ZERO_HASH} from "@lodestar/state-transition";
import {Web3} from "web3";
import {
EL_GENESIS_ACCOUNT,
EL_GENESIS_PASSWORD,
EL_GENESIS_SECRET_KEY,
SHARED_JWT_SECRET,
SIM_ENV_NETWORK_ID,
} from "../../constants.js";
import {Eth1ProviderWithAdmin} from "../../eth1ProviderWithAdmin.js";
import {registerWeb3JsPlugins} from "../../web3JsPlugins.js";
import {ExecutionClient, ExecutionNodeGenerator, ExecutionStartMode, JobOptions, RunnerType} from "../../interfaces.js";
import {getNodeMountedPaths} from "../../utils/paths.js";
import {getNodePorts} from "../../utils/ports.js";
Expand Down Expand Up @@ -166,11 +165,8 @@ export const generateGethNode: ExecutionNodeGenerator<ExecutionClient.Geth> = (o

const job = runner.create([{...initJobOptions, children: [{...importJobOptions, children: [startJobOptions]}]}]);

const provider = new Eth1ProviderWithAdmin(
{DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH},
// To allow admin_* RPC methods had to add "ethRpcUrl"
{providerUrls: [ethRpcPublicUrl, engineRpcPublicUrl], jwtSecretHex: SHARED_JWT_SECRET}
);
const provider = new Web3(ethRpcPublicUrl);
registerWeb3JsPlugins(provider);

return {
client: ExecutionClient.Geth,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {writeFile} from "node:fs/promises";
import path from "node:path";
import got from "got";
import {ZERO_HASH} from "@lodestar/state-transition";
import {Eth1ProviderWithAdmin} from "../../eth1ProviderWithAdmin.js";
import {Web3} from "web3";
import {registerWeb3JsPlugins} from "../../web3JsPlugins.js";
import {ExecutionClient, ExecutionNodeGenerator, JobOptions, RunnerType} from "../../interfaces.js";
import {getNethermindChainSpec} from "../../utils/executionGenesis.js";
import {getNodeMountedPaths} from "../../utils/paths.js";
Expand Down Expand Up @@ -130,11 +129,8 @@ export const generateNethermindNode: ExecutionNodeGenerator<ExecutionClient.Neth

const job = runner.create([startJobOptions]);

const provider = new Eth1ProviderWithAdmin(
{DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH},
// To allow admin_* RPC methods had to add "ethRpcUrl"
{providerUrls: [ethRpcPublicUrl, engineRpcPublicUrl], jwtSecretHex: SHARED_JWT_SECRET}
);
const provider = new Web3(ethRpcPublicUrl);
registerWeb3JsPlugins(provider);

return {
client: ExecutionClient.Nethermind,
Expand Down
53 changes: 0 additions & 53 deletions packages/cli/test/utils/simulation/eth1ProviderWithAdmin.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/cli/test/utils/simulation/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {ChildProcess} from "node:child_process";
import type {SecretKey} from "@chainsafe/bls/types";
import {Web3} from "web3";
import {Api} from "@lodestar/api";
import {Api as KeyManagerApi} from "@lodestar/api/keymanager";
import {ChainForkConfig} from "@lodestar/config";
Expand All @@ -11,7 +12,6 @@ import {BeaconArgs} from "../../../src/cmds/beacon/options.js";
import {IValidatorCliArgs} from "../../../src/cmds/validator/options.js";
import {GlobalArgs} from "../../../src/options/index.js";
import {EpochClock} from "./epochClock.js";
import {Eth1ProviderWithAdmin} from "./eth1ProviderWithAdmin.js";

export type NodeId = string;

Expand Down Expand Up @@ -208,7 +208,7 @@ export interface ExecutionNode<E extends ExecutionClient = ExecutionClient> {
*/
readonly ethRpcPrivateUrl: string;
readonly jwtSecretHex: string;
readonly provider: E extends ExecutionClient.Mock ? null : Eth1ProviderWithAdmin;
readonly provider: E extends ExecutionClient.Mock ? null : Web3;
readonly job: Job;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/test/utils/simulation/simulationEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ export class SimulationEnvironment {
const el = this.nodes[i].execution;

// If eth1 is mock then genesis hash would be empty
const eth1Genesis = el.provider === null ? {hash: MOCK_ETH1_GENESIS_HASH} : await el.provider.getBlockByNumber(0);
const eth1Genesis = el.provider === null ? {hash: MOCK_ETH1_GENESIS_HASH} : await el.provider?.eth.getBlock(0);

if (!eth1Genesis) {
if (!eth1Genesis.hash) {
throw new Error(`Eth1 genesis not found for node "${this.nodes[i].id}"`);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/test/utils/simulation/utils/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function connectNewCLNode(newNode: BeaconNode, nodes: BeaconNode[])
}

export async function connectNewELNode(newNode: ExecutionNode, nodes: ExecutionNode[]): Promise<void> {
const elIdentity = newNode.provider === null ? null : await newNode.provider.admin.nodeInfo();
const elIdentity = newNode.provider === null ? null : await newNode.provider?.admin.nodeInfo();
if (elIdentity && !elIdentity.enode) return;

for (const node of nodes) {
Expand All @@ -54,6 +54,7 @@ export async function connectNewELNode(newNode: ExecutionNode, nodes: ExecutionN
// Nethermind had a bug in admin_addPeer RPC call
// https://github.com/NethermindEth/nethermind/issues/4876
if (node.provider !== null && node.client !== ExecutionClient.Nethermind && elIdentity) {
// `web3.admin` here refers to the Web3 plugin `Web3AdminPlugin`
matthewkeil marked this conversation as resolved.
Show resolved Hide resolved
await node.provider.admin.addPeer(elIdentity.enode);
}
}
Expand Down
47 changes: 47 additions & 0 deletions packages/cli/test/utils/simulation/web3JsPlugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {Web3PluginBase, Web3} from "web3";

class Web3AdminPlugin extends Web3PluginBase {
/**
* The admin plugin as available via the provider object
* like in the example below.
*
* await node.web3.admin.addPeer(elIdentity.enode);
*/
pluginNamespace = "admin";

async nodeInfo(): Promise<{
enode: string;
id: string;
ip: string;
listenAddr: string;
name: string;
ports: {
discovery: number;
listener: number;
};
protocols: {
eth: {
difficulty: number;
genesis: string;
head: string;
network: number;
};
};
}> {
return this.requestManager.send({method: "admin_nodeInfo", params: []});
}

async addPeer(enode: string): Promise<boolean> {
return this.requestManager.send({method: "admin_addPeer", params: [enode]});
}
}

declare module "web3" {
interface Web3Context {
admin: Web3AdminPlugin;
}
}

export function registerWeb3JsPlugins(web3: Web3): void {
web3.registerPlugin(new Web3AdminPlugin());
}
Loading