Skip to content

Commit

Permalink
Add support for 4844 tests in sim tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nazarhussain committed Apr 8, 2024
1 parent 8c93a3f commit 4a7fd0f
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 22 deletions.
67 changes: 47 additions & 20 deletions packages/cli/test/sim/deneb.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,78 @@
/* eslint-disable @typescript-eslint/naming-convention */
import path from "node:path";
import {activePreset} from "@lodestar/params";
import {SimulationEnvironment} from "../utils/simulation/simulationEnvironment.js";
import {nodeAssertion} from "../utils/simulation/assertions/nodeAssertion.js";
import {AssertionMatch, BeaconClient, ExecutionClient} from "../utils/simulation/interfaces.js";
import {BeaconClient, ExecutionClient, ValidatorClient} from "../utils/simulation/interfaces.js";
import {defineSimTestConfig, logFilesDir} from "../utils/simulation/utils/index.js";
import {connectAllNodes, waitForSlot} from "../utils/simulation/utils/network.js";
import {assertRangeSync, assertCheckpointSync} from "../utils/simulation/utils/syncing.js";
import {createBlobsAssertion} from "../utils/simulation/assertions/blobsAssertion.js";
import {assertCheckpointSync, assertRangeSync} from "../utils/simulation/utils/syncing.js";

const altairForkEpoch = 2;
const bellatrixForkEpoch = 4;
const runTillEpoch = 6;
const syncWaitEpoch = 2;

const {estimatedTimeoutMs, forkConfig} = defineSimTestConfig({
ALTAIR_FORK_EPOCH: altairForkEpoch,
BELLATRIX_FORK_EPOCH: bellatrixForkEpoch,
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: 0,
runTillEpoch: runTillEpoch + syncWaitEpoch,
initialNodes: 2,
additionalSlotsForTTD: 0,
});

const env = await SimulationEnvironment.initWithDefaults(
{
id: "deneb",
logsDir: path.join(logFilesDir, "deneb"),
forkConfig,
trustedSetup: true,
},
[
{id: "node-1", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Mock, keysCount: 32},
{id: "node-2", beacon: BeaconClient.Lodestar, execution: ExecutionClient.Mock, keysCount: 32, remote: true},
{
id: "node-1",
beacon: BeaconClient.Lodestar,
validator: {
type: ValidatorClient.Lodestar,
options: {
clientOptions: {
useProduceBlockV3: true,
},
},
},
execution: ExecutionClient.Geth,
keysCount: 32,
mining: true,
},
{
id: "node-2",
beacon: BeaconClient.Lodestar,
validator: {
type: ValidatorClient.Lodestar,
options: {
clientOptions: {
useProduceBlockV3: true,
},
},
},
execution: ExecutionClient.Geth,
keysCount: 32,
remote: true,
},
]
);

env.tracker.register({
...nodeAssertion,
match: ({slot}) => {
return slot === 1 ? AssertionMatch.Assert | AssertionMatch.Capture | AssertionMatch.Remove : AssertionMatch.None;
},
});

await env.start({runTimeoutMs: estimatedTimeoutMs});
await connectAllNodes(env.nodes);

// The `TTD` will be reach around `start of bellatrixForkEpoch + additionalSlotsForMerge` slot
// We wait for the end of that epoch with half more epoch to make sure merge transition is complete
env.tracker.register(
createBlobsAssertion(env.nodes, {
sendBlobsAtSlot: 2,
validateBlobsAt: env.clock.getLastSlotOfEpoch(2),
})
);

await waitForSlot("Waiting for the 2nd epoch to pass", {
slot: env.clock.getLastSlotOfEpoch(bellatrixForkEpoch) + activePreset.SLOTS_PER_EPOCH / 2,
slot: env.clock.getLastSlotOfEpoch(2),
env,
});

Expand Down
77 changes: 77 additions & 0 deletions packages/cli/test/utils/simulation/assertions/blobsAssertion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {randomBytes} from "node:crypto";
import {ApiError} from "@lodestar/api";
import {fromHex, toHex} from "@lodestar/utils";
import {SimulationAssertion, AssertionMatch, AssertionResult, NodePair} from "../interfaces.js";
import {EL_GENESIS_ACCOUNT, EL_GENESIS_SECRET_KEY, SIM_ENV_CHAIN_ID} from "../constants.js";
import {generateBlobsForTransaction} from "../utils/blobs.js";
import {BlobsEIP4844Transaction} from "../Web3Plugins.js";

const numberOfBlobs = 6;

export function createBlobsAssertion(
nodes: NodePair[],
{sendBlobsAtSlot, validateBlobsAt}: {sendBlobsAtSlot: number; validateBlobsAt: number}
): SimulationAssertion<string, number> {
return {
id: `blobs-${nodes.map((n) => n.id).join("-")}`,
match: ({slot}) => {
// Run capture every sendBlobsAtSlot -> validateBlobsAt and validate only at validateBlobsAt
return slot === validateBlobsAt
? AssertionMatch.Capture | AssertionMatch.Assert
: slot >= sendBlobsAtSlot && slot <= validateBlobsAt
? AssertionMatch.Capture
: AssertionMatch.None;
},

async capture({slot, node}) {
// TODO: Add feature to detect the node index during capture process
// slot starts from 1, so we add up 1
if (slot <= numberOfBlobs + 1 && node.id === "node-1") {
const {blobs, kzgCommitments, blobVersionedHashes, kzgProofs} = generateBlobsForTransaction(1);
const nonce = await node.execution.provider?.eth.getTransactionCount(EL_GENESIS_ACCOUNT);
const tx = BlobsEIP4844Transaction.fromTxData({
chainId: `0x${SIM_ENV_CHAIN_ID.toString(16)}`,
to: `0x${randomBytes(20).toString("hex")}`,
gasLimit: "0xc350",
maxPriorityFeePerGas: "0x3b9aca00",
maxFeePerGas: "0x3ba26b20",
maxFeePerBlobGas: "0x3e8",
value: "0x10000",
nonce: `0x${(nonce ?? 0).toString(16)}`,
blobVersionedHashes,
blobs,
kzgCommitments,
kzgProofs,
});
const signedTx = tx.sign(fromHex(`0x${EL_GENESIS_SECRET_KEY}`));
await node.execution.provider?.extended.sendRawTransaction(toHex(signedTx.serialize()));
}

const blobSideCars = await node.beacon.api.beacon.getBlobSidecars(slot);
ApiError.assert(blobSideCars);

return blobSideCars.response.data.length;
},

assert: async ({store}) => {
const errors: AssertionResult[] = [];

let eip4844Blobs = 0;
for (let slot = sendBlobsAtSlot; slot <= validateBlobsAt; slot++) {
eip4844Blobs += store[slot] ?? 0;
}

if (eip4844Blobs !== numberOfBlobs) {
errors.push([
"Node does not have right number of blobs",
{
expectedBlobs: numberOfBlobs,
currentBlobs: eip4844Blobs,
},
]);
}

return errors;
},
};
}
2 changes: 1 addition & 1 deletion packages/cli/test/utils/simulation/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const SIM_ENV_NETWORK_ID = 1234;
export const LODESTAR_BINARY_PATH = `${__dirname}/../../../bin/lodestar.js`;
export const MOCK_ETH1_GENESIS_HASH = "0xfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfb";
export const SHARED_JWT_SECRET = "0xdc6457099f127cf0bac78de8b297df04951281909db4f58b43def7c7151e765d";
export const SHARED_VALIDATOR_PASSWORD = "passwrod";
export const SHARED_VALIDATOR_PASSWORD = "password";
export const EL_GENESIS_SECRET_KEY = "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8";
export const EL_GENESIS_PASSWORD = "12345678";
export const EL_GENESIS_ACCOUNT = "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b";
2 changes: 2 additions & 0 deletions packages/cli/test/utils/simulation/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type SimulationInitOptions = {
id: string;
logsDir: string;
forkConfig: ChainForkConfig;
trustedSetup?: boolean;
};

export type SimulationOptions = {
Expand All @@ -27,6 +28,7 @@ export type SimulationOptions = {
rootDir: string;
controller: AbortController;
genesisTime: number;
trustedSetup?: boolean;
};

export enum BeaconClient {
Expand Down
9 changes: 8 additions & 1 deletion packages/cli/test/utils/simulation/simulationEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from "node:path";
import tmp from "tmp";
import {fromHexString} from "@chainsafe/ssz";
import {nodeUtils} from "@lodestar/beacon-node";
import {loadEthereumTrustedSetup, initCKZG} from "@lodestar/beacon-node/util";
import {ChainForkConfig} from "@lodestar/config";
import {activePreset} from "@lodestar/params";
import {BeaconStateAllForks, interopSecretKey} from "@lodestar/state-transition";
Expand Down Expand Up @@ -85,14 +86,15 @@ export class SimulationEnvironment {
}

static async initWithDefaults(
{forkConfig, logsDir, id}: SimulationInitOptions,
{forkConfig, logsDir, id, trustedSetup}: SimulationInitOptions,
clients: NodePairDefinition[]
): Promise<SimulationEnvironment> {
const env = new SimulationEnvironment(forkConfig, {
logsDir,
id,
genesisTime: Math.floor(Date.now() / 1000),
controller: new AbortController(),
trustedSetup,
rootDir: path.join(tmp.dirSync({unsafeCleanup: true, tmpdir: "/tmp", template: "sim-XXXXXX"}).name, id),
});

Expand All @@ -111,6 +113,11 @@ export class SimulationEnvironment {
).toISOString()} simulationTimeout=${prettyMsToTime(opts.runTimeoutMs)} rootDir=${this.options.rootDir}`
);

if (this.options.trustedSetup) {
await initCKZG();
loadEthereumTrustedSetup();
}

if (opts.runTimeoutMs > 0) {
this.runTimeout = setTimeout(() => {
const slots = this.clock.getSlotFor((currentTime + opts.runTimeoutMs) / MS_IN_SEC);
Expand Down
40 changes: 40 additions & 0 deletions packages/cli/test/utils/simulation/utils/blobs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {digest as sha256Digest} from "@chainsafe/as-sha256";
import {ckzg} from "@lodestar/beacon-node/util";
import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB, VERSIONED_HASH_VERSION_KZG} from "@lodestar/params";
import {deneb} from "@lodestar/types";
import {toHex} from "@lodestar/utils";

export function generateBlobsForTransaction(count: number): {
blobs: string[];
kzgCommitments: string[];
blobVersionedHashes: string[];
kzgProofs: string[];
} {
const blobs = Array.from({length: count}, () => generateRandomBlob());
const kzgCommitments = blobs.map((blob) => ckzg.blobToKzgCommitment(blob));
const versionedHash = kzgCommitments.map((kzgCommitment) => kzgCommitmentToVersionedHash(kzgCommitment));
const kzgProofs = blobs.map((blob, index) => ckzg.computeBlobKzgProof(blob, kzgCommitments[index]));

return {
blobs: blobs.map((blob) => toHex(blob)),
kzgCommitments: kzgCommitments.map((kzgCommitment) => toHex(kzgCommitment)),
blobVersionedHashes: versionedHash.map((hash) => toHex(hash)),
kzgProofs: kzgProofs.map((proof) => toHex(proof)),
};
}

function generateRandomBlob(): deneb.Blob {
const blob = new Uint8Array(FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT);
const dv = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
for (let i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) {
dv.setUint32(i * BYTES_PER_FIELD_ELEMENT, i);
}
return blob;
}

export function kzgCommitmentToVersionedHash(kzgCommitment: deneb.KZGCommitment): Uint8Array {
const hash = sha256Digest(kzgCommitment);
// Equivalent to `VERSIONED_HASH_VERSION_KZG + hash(kzg_commitment)[1:]`
hash[0] = VERSIONED_HASH_VERSION_KZG;
return hash;
}
Loading

0 comments on commit 4a7fd0f

Please sign in to comment.