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

Enable using an engine endpoint for eth1 deposit/merge tracker #3949

Merged
merged 6 commits into from
Apr 30, 2022
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
8 changes: 4 additions & 4 deletions .github/workflows/test-sim-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name: Sim merge tests
on: [pull_request, push]

env:
GETH_COMMIT: e0e8bf31c5d44f7de33ce774b221debf2c42256c
NETHERMIND_COMMIT: 29ffe2630afb69120004ff79bde70d37a3b80b7b
GETH_COMMIT: bb5633c5ee3975ce016636066ec790054ec469e4
NETHERMIND_COMMIT: 00b50532543824dbac65e8b7ab09484e44992c27

jobs:
sim-merge-tests:
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
EL_BINARY_DIR: ../../go-ethereum/build/bin
EL_SCRIPT_DIR: kiln/geth
ENGINE_PORT: 8551
EL_PORT: 8545
ETH_PORT: 8545
TX_SCENARIOS: simple

# Install Nethermind merge interop
Expand All @@ -69,8 +69,8 @@ jobs:
env:
EL_BINARY_DIR: ../../nethermind/src/Nethermind/Nethermind.Runner
EL_SCRIPT_DIR: kiln/nethermind
EL_PORT: 8550
ENGINE_PORT: 8551
ETH_PORT: 8545

- name: Upload debug log test files
if: ${{ always() }}
Expand Down
2 changes: 1 addition & 1 deletion kiln/devnets/kiln.vars
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ BESU_IMAGE=hyperledger/besu:develop

LODESTAR_IMAGE=chainsafe/lodestar:next

LODESTAR_EXTRA_ARGS="--eth1.providerUrls http://127.0.0.1:8545 --execution.urls http://127.0.0.1:8551 --api.rest.enabled --api.rest.host 0.0.0.0 --api.rest.api '*'"
LODESTAR_EXTRA_ARGS="--execution.urls http://127.0.0.1:8551 --api.rest.enabled --api.rest.host 0.0.0.0 --api.rest.api '*'"

LODESTAR_VALIDATOR_ARGS='--network kiln --fromMnemonic "lens risk clerk foot verb planet drill roof boost aim salt omit celery tube list permit motor obvious flash demise churn hold wave hollow" --mnemonicIndexes 0..5'

Expand Down
2 changes: 1 addition & 1 deletion kiln/geth/post-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ currentDir=$(pwd)

. $scriptDir/common-setup.sh

$EL_BINARY_DIR/geth --http --ws -http.api "engine,net,eth" --datadir $DATA_DIR --allow-insecure-unlock --unlock $pubKey --password $DATA_DIR/password.txt --authrpc.jwtsecret $currentDir/$DATA_DIR/jwtsecret
$EL_BINARY_DIR/geth --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret $currentDir/$DATA_DIR/jwtsecret --datadir $DATA_DIR --allow-insecure-unlock --unlock $pubKey --password $DATA_DIR/password.txt
2 changes: 1 addition & 1 deletion kiln/geth/pre-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ currentDir=$(pwd)

. $scriptDir/common-setup.sh

$EL_BINARY_DIR/geth --http --ws -http.api "engine,net,eth,miner" --datadir $DATA_DIR --allow-insecure-unlock --unlock $pubKey --password $DATA_DIR/password.txt --nodiscover --mine --authrpc.jwtsecret $currentDir/$DATA_DIR/jwtsecret
$EL_BINARY_DIR/geth --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret $currentDir/$DATA_DIR/jwtsecret --datadir $DATA_DIR --allow-insecure-unlock --unlock $pubKey --password $DATA_DIR/password.txt --nodiscover --mine
2 changes: 1 addition & 1 deletion kiln/gethdocker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ RUN cd /go-ethereum && go run build/ci.go install ./cmd/geth
FROM alpine:latest
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/

EXPOSE 8545 8546 30303 30303/udp
EXPOSE 8545 8551 30303 30303/udp
ENTRYPOINT ["geth"]
2 changes: 1 addition & 1 deletion kiln/gethdocker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ docker build . --tag geth:kiln

```bash
cd packages/lodestar
EL_BINARY_DIR=geth:kiln EL_SCRIPT_DIR=kiln/gethdocker EL_PORT=8545 ENGINE_PORT=8551 TX_SCENARIOS=simple yarn mocha test/sim/merge-interop.test.ts
EL_BINARY_DIR=geth:kiln EL_SCRIPT_DIR=kiln/gethdocker ETH_PORT=8545 ENGINE_PORT=8551 TX_SCENARIOS=simple yarn mocha test/sim/merge-interop.test.ts
```
2 changes: 1 addition & 1 deletion kiln/gethdocker/post-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ currentDir=$(pwd)

. $scriptDir/common-setup.sh

docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --http --ws -http.api "engine,net,eth" --allow-insecure-unlock --unlock $pubKey --password /data/password.txt --datadir /data --authrpc.jwtsecret /data/jwtsecret
docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret /data/jwtsecret --allow-insecure-unlock --unlock $pubKey --password /data/password.txt --datadir /data
2 changes: 1 addition & 1 deletion kiln/gethdocker/pre-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ currentDir=$(pwd)
. $scriptDir/common-setup.sh

# EL_BINARY_DIR refers to the local docker image build from kiln/gethdocker folder
docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --http --ws -http.api "engine,net,eth,miner" --allow-insecure-unlock --unlock $pubKey --password /data/password.txt --datadir /data --nodiscover --mine --authrpc.jwtsecret /data/jwtsecret
docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret /data/jwtsecret --allow-insecure-unlock --unlock $pubKey --password /data/password.txt --datadir /data --nodiscover --mine
2 changes: 1 addition & 1 deletion kiln/nethermind/post-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ currentDir=$(pwd)
. $scriptDir/common-setup.sh

cd $EL_BINARY_DIR
dotnet run -c Release -- --config themerge_kiln_testvectors --Merge.TerminalTotalDifficulty $TTD --JsonRpc.JwtSecretFile $currentDir/$DATA_DIR/jwtsecret
dotnet run -c Release -- --config themerge_kiln_testvectors --Merge.TerminalTotalDifficulty $TTD --JsonRpc.JwtSecretFile $currentDir/$DATA_DIR/jwtsecret --JsonRpc.Enabled true --JsonRpc.Host 0.0.0.0 --JsonRpc.AdditionalRpcUrls "http://localhost:$ETH_PORT|http|net;eth;subscribe;engine;web3;client|no-auth,http://localhost:$ENGINE_PORT|http|eth;engine"
2 changes: 1 addition & 1 deletion kiln/nethermind/pre-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ currentDir=$(pwd)
. $scriptDir/common-setup.sh

cd $EL_BINARY_DIR
dotnet run -c Release -- --config themerge_kiln_m2 --Merge.TerminalTotalDifficulty $TTD --JsonRpc.JwtSecretFile $currentDir/$DATA_DIR/jwtsecret
dotnet run -c Release -- --config themerge_kiln_m2 --Merge.TerminalTotalDifficulty $TTD --JsonRpc.JwtSecretFile $currentDir/$DATA_DIR/jwtsecret --Merge.Enabled true --Init.DiagnosticMode=None --JsonRpc.Enabled true --JsonRpc.Host 0.0.0.0 --JsonRpc.AdditionalRpcUrls "http://localhost:$ETH_PORT|http|net;eth;subscribe;engine;web3;client|no-auth,http://localhost:$ENGINE_PORT|http|eth;engine"
26 changes: 21 additions & 5 deletions packages/cli/src/options/beaconNodeOptions/eth1.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import fs from "node:fs";
import {defaultOptions, IBeaconNodeOptions} from "@chainsafe/lodestar";
import {ICliCommandOptions} from "../../util";
import {ICliCommandOptions, extractJwtHexSecret} from "../../util";
import {ExecutionEngineArgs} from "./execution";

export interface IEth1Args {
"eth1.enabled": boolean;
Expand All @@ -10,17 +12,30 @@ export interface IEth1Args {
"eth1.unsafeAllowDepositDataOverwrite": boolean;
}

export function parseArgs(args: IEth1Args): IBeaconNodeOptions["eth1"] {
export function parseArgs(args: IEth1Args & Partial<ExecutionEngineArgs>): IBeaconNodeOptions["eth1"] {
// Support deprecated flag 'eth1.providerUrl' only if 'eth1.providerUrls' is not defined
// Safe default to '--eth1.providerUrl' only if it's defined. Prevent returning providerUrls: [undefined]
let jwtSecretHex: string | undefined;
let providerUrls = args["eth1.providerUrls"];
if (providerUrls !== undefined && args["eth1.providerUrl"]) {
if (providerUrls === undefined && args["eth1.providerUrl"]) {
providerUrls = [args["eth1.providerUrl"]];
}

// If no providerUrls are explicitly provided, we should pick the execution endpoint
// because as per Kiln spec v2.1, execution *must* host the `eth_` methods necessary
// for deposit and merge trackers on engine endpoints as well protected by a
// jwt auth mechanism.
if (providerUrls === undefined && args["execution.urls"]) {
providerUrls = args["execution.urls"];
jwtSecretHex = args["jwt-secret"]
? extractJwtHexSecret(fs.readFileSync(args["jwt-secret"], "utf-8").trim())
: undefined;
}

return {
enabled: args["eth1.enabled"],
providerUrls: providerUrls,
providerUrls,
jwtSecretHex,
depositContractDeployBlock: args["eth1.depositContractDeployBlock"],
disableEth1DepositDataTracker: args["eth1.disableEth1DepositDataTracker"],
unsafeAllowDepositDataOverwrite: args["eth1.unsafeAllowDepositDataOverwrite"],
Expand All @@ -43,7 +58,8 @@ export const options: ICliCommandOptions<IEth1Args> = {
},

"eth1.providerUrls": {
description: "Urls to Eth1 node with enabled rpc",
description:
"Urls to Eth1 node with enabled rpc. If not explicity provided and execution endpoint provided via execution.urls, it will use execution.urls. Otherwise will try connecting on the specified default(s)",
type: "array",
defaultDescription: defaultOptions.eth1.providerUrls.join(" "),
group: "eth1",
Expand Down
26 changes: 26 additions & 0 deletions packages/cli/test/unit/options/beaconNodeOptions.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {expect} from "chai";
import fs from "node:fs";
import {IBeaconNodeOptions} from "@chainsafe/lodestar";
import {LogLevel, RecursivePartial} from "@chainsafe/lodestar-utils";
import {parseBeaconNodeArgs, IBeaconNodeArgs} from "../../../src/options/beaconNodeOptions";
import {getTestdirPath} from "../../utils";

describe("options / beaconNodeOptions", () => {
it("Should parse BeaconNodeArgs", () => {
Expand Down Expand Up @@ -129,4 +131,28 @@ describe("options / beaconNodeOptions", () => {
const options = parseBeaconNodeArgs(beaconNodeArgsPartial);
expect(options).to.deep.equal(expectedOptions);
});

it("Should use execution endpoint & jwt for eth1", () => {
const jwtSecretFile = getTestdirPath("./jwtsecret");
const jwtSecretHex = "0xdc6457099f127cf0bac78de8b297df04951281909db4f58b43def7c7151e765d";
fs.writeFileSync(jwtSecretFile, jwtSecretHex, {encoding: "utf8"});

// Cast to match the expected fully defined type
const beaconNodeArgsPartial = {
"eth1.enabled": true,
"execution.urls": ["http://my.node:8551"],
"jwt-secret": jwtSecretFile,
} as IBeaconNodeArgs;

const expectedOptions: RecursivePartial<IBeaconNodeOptions> = {
eth1: {
enabled: true,
providerUrls: ["http://my.node:8551"],
jwtSecretHex,
},
};

const options = parseBeaconNodeArgs(beaconNodeArgsPartial);
expect(options.eth1).to.deep.equal(expectedOptions.eth1);
});
});
5 changes: 5 additions & 0 deletions packages/lodestar/src/eth1/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ export type Eth1Options = {
enabled: boolean;
disableEth1DepositDataTracker?: boolean;
providerUrls: string[];
/**
* jwtSecretHex is the jwt secret if the eth1 modules should ping the jwt auth
* protected engine endpoints.
*/
jwtSecretHex?: string;
depositContractDeployBlock?: number;
unsafeAllowDepositDataOverwrite: boolean;
};
Expand Down
5 changes: 4 additions & 1 deletion packages/lodestar/src/eth1/provider/eth1Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {toHexString} from "@chainsafe/ssz";
import {phase0} from "@chainsafe/lodestar-types";
import {AbortSignal} from "@chainsafe/abort-controller";
import {IChainConfig} from "@chainsafe/lodestar-config";
import {fromHex} from "@chainsafe/lodestar-utils";

import {chunkifyInclusiveRange} from "../../util/chunkify";
import {linspace} from "../../util/numpy";
import {retry} from "../../util/retry";
Expand Down Expand Up @@ -43,7 +45,7 @@ export class Eth1Provider implements IEth1Provider {

constructor(
config: Pick<IChainConfig, "DEPOSIT_CONTRACT_ADDRESS">,
opts: Pick<Eth1Options, "depositContractDeployBlock" | "providerUrls">,
opts: Pick<Eth1Options, "depositContractDeployBlock" | "providerUrls" | "jwtSecretHex">,
signal?: AbortSignal
) {
this.deployBlock = opts.depositContractDeployBlock ?? 0;
Expand All @@ -52,6 +54,7 @@ export class Eth1Provider implements IEth1Provider {
signal,
// Don't fallback with is truncated error. Throw early and let the retry on this class handle it
shouldNotFallback: isJsonRpcTruncatedError,
jwtSecret: opts.jwtSecretHex ? fromHex(opts.jwtSecretHex) : undefined,
});
}

Expand Down
28 changes: 17 additions & 11 deletions packages/lodestar/test/sim/merge-interop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ import {bytesToData, dataToBytes, quantityToNum} from "../../src/eth1/provider/u
// EL_BINARY_DIR: File path to locate the EL executable
// EL_SCRIPT_DIR: Directory in packages/lodestar for the EL client, from where to
// execute post-merge/pre-merge EL scenario scripts
// EL_PORT: EL port on localhost for hosting both engine & json rpc endpoints
// ETH_PORT: EL port on localhost hosting non auth protected eth_ methods
// ENGINE_PORT: Specify the port on which an jwt auth protected engine api is being hosted,
// typically by default at 8551 for geth. Some ELs could host it as same port as eth_ apis,
// but just with the engine_ methods protected. In that case this param can be skipped
// TX_SCENARIOS: comma seprated transaction scenarios this EL client build supports
// Example:
// ```
// $ EL_BINARY_DIR=/home/lion/Code/eth2.0/merge-interop/go-ethereum/build/bin \
// EL_SCRIPT_DIR=kiln/geth EL_PORT=8545 ENGINE_PORT=8551 TX_SCENARIOS=simple \
// EL_SCRIPT_DIR=kiln/geth ETH_PORT=8545 ENGINE_PORT=8551 TX_SCENARIOS=simple \
// ../../node_modules/.bin/mocha test/sim/merge.test.ts
// ```

Expand All @@ -52,8 +52,10 @@ describe("executionEngine / ExecutionEngineHttp", function () {
this.timeout("10min");

const dataPath = fs.mkdtempSync("lodestar-test-merge-interop");
const jsonRpcPort = process.env.EL_PORT;
const enginePort = process.env.ENGINE_PORT ?? jsonRpcPort;
const jsonRpcPort = process.env.ETH_PORT;
const enginePort = process.env.ENGINE_PORT;

/** jsonRpcUrl is used only for eth transactions or to check if EL online/offline */
const jsonRpcUrl = `http://localhost:${jsonRpcPort}`;
const engineApiUrl = `http://localhost:${enginePort}`;

Expand Down Expand Up @@ -115,9 +117,9 @@ describe("executionEngine / ExecutionEngineHttp", function () {
// $ ./go-ethereum/build/bin/geth --catalyst --datadir "~/ethereum/taunus" init genesis.json
// $ ./build/bin/geth --catalyst --http --ws -http.api "engine" --datadir "~/ethereum/taunus" console
async function runEL(elScript: string, ttd: number): Promise<{genesisBlockHash: string}> {
if (!process.env.EL_BINARY_DIR || !process.env.EL_SCRIPT_DIR || !process.env.EL_PORT) {
if (!process.env.EL_BINARY_DIR || !process.env.EL_SCRIPT_DIR || !process.env.ENGINE_PORT || !process.env.ETH_PORT) {
throw Error(
`EL ENV must be provided, EL_BINARY_DIR: ${process.env.EL_BINARY_DIR}, EL_SCRIPT_DIR: ${process.env.EL_SCRIPT_DIR}, EL_PORT: ${process.env.EL_PORT}`
`EL ENV must be provided, EL_BINARY_DIR: ${process.env.EL_BINARY_DIR}, EL_SCRIPT_DIR: ${process.env.EL_SCRIPT_DIR}, ENGINE_PORT: ${process.env.ENGINE_PORT}, ETH_PORT: ${process.env.ETH_PORT}`
);
}

Expand All @@ -136,7 +138,7 @@ describe("executionEngine / ExecutionEngineHttp", function () {
await waitForELOnline(jsonRpcUrl, controller.signal);

// Fetch genesis block hash
const genesisBlockHash = await getGenesisBlockHash(jsonRpcUrl, controller.signal);
const genesisBlockHash = await getGenesisBlockHash({providerUrl: engineApiUrl, jwtSecretHex}, controller.signal);
return {genesisBlockHash};
}

Expand Down Expand Up @@ -319,7 +321,8 @@ describe("executionEngine / ExecutionEngineHttp", function () {
api: {rest: {enabled: true} as RestApiOptions},
sync: {isSingleNode: true},
network: {allowPublishToZeroPeers: true, discv5: null},
eth1: {enabled: true, providerUrls: [jsonRpcUrl]},
// Now eth deposit/merge tracker methods directly available on engine endpoints
eth1: {enabled: true, providerUrls: [engineApiUrl], jwtSecretHex},
executionEngine: {urls: [engineApiUrl], jwtSecretHex},
},
validatorCount: validatorClientCount * validatorsPerClient,
Expand Down Expand Up @@ -418,7 +421,7 @@ describe("executionEngine / ExecutionEngineHttp", function () {

// Assertions to make sure the end state is good
// 1. The proper head is set
const rpc = new Eth1Provider({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}, {providerUrls: [jsonRpcUrl]});
const rpc = new Eth1Provider({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}, {providerUrls: [engineApiUrl], jwtSecretHex});
const consensusHead = bn.chain.forkChoice.getHead();
const executionHeadBlockNumber = await rpc.getBlockNumber();
const executionHeadBlock = await rpc.getBlockByNumber(executionHeadBlockNumber);
Expand Down Expand Up @@ -502,10 +505,13 @@ async function isPortInUse(port: number): Promise<boolean> {
});
}

async function getGenesisBlockHash(url: string, signal: AbortSignal): Promise<string> {
async function getGenesisBlockHash(
{providerUrl, jwtSecretHex}: {providerUrl: string; jwtSecretHex?: string},
signal: AbortSignal
): Promise<string> {
const eth1Provider = new Eth1Provider(
({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH} as Partial<IChainConfig>) as IChainConfig,
{providerUrls: [url]},
{providerUrls: [providerUrl], jwtSecretHex},
signal
);

Expand Down