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

feat: add revert tests (external node) to zk_toolbox #2408

Merged
merged 40 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2de050a
wip: add revert en tests to zk_supervisor
aon Jul 1, 2024
0c60206
Fix typo
manuelmauro Jul 5, 2024
7757299
Format code
manuelmauro Jul 5, 2024
9c07368
Run nodes with config
manuelmauro Jul 8, 2024
f68833e
Format
manuelmauro Jul 8, 2024
106e0c7
Use correct operator account
manuelmauro Jul 9, 2024
108160c
Merge branch 'main' into aon-add-revert-en-tests-zk-supervisor
manuelmauro Jul 9, 2024
ef61851
Format
manuelmauro Jul 9, 2024
e6717e5
Source operator account from config
manuelmauro Jul 9, 2024
76cf2d2
Format
manuelmauro Jul 9, 2024
e044a68
Remove logging
manuelmauro Jul 9, 2024
6c9982e
Remove unnecessary sleep
manuelmauro Jul 9, 2024
3715176
Cover the case of env configuration when running external node
manuelmauro Jul 9, 2024
2da4b1e
Comment on edge cases
manuelmauro Jul 9, 2024
098013a
Remove unused env
manuelmauro Jul 9, 2024
dd0bbd3
Add CI test
manuelmauro Jul 10, 2024
60a6876
Add optional env parameter to runServerInBackground
manuelmauro Jul 10, 2024
552cbe3
Implement previous commit by duplicating utils
manuelmauro Jul 10, 2024
8c8798c
Reduce code duplication
manuelmauro Jul 10, 2024
4615df5
Use extEnv for block reverter
manuelmauro Jul 10, 2024
9aead67
Fix reverter conf
manuelmauro Jul 10, 2024
4294b14
Merge branch 'main' into aon-add-revert-en-tests-zk-supervisor
manuelmauro Jul 12, 2024
fa0ecda
Improve options type
manuelmauro Jul 12, 2024
7d6210c
Add sleep to reduce test flaky-ness
manuelmauro Jul 12, 2024
f8cb5f6
Format
manuelmauro Jul 12, 2024
15f71cf
Remove global test configuration
manuelmauro Jul 12, 2024
4590575
Do not rely on test timeote for node execution failure
manuelmauro Jul 12, 2024
06d0f97
Restore ExtNode.waitForExit
manuelmauro Jul 12, 2024
11c0270
Kill MainNode before alice.getBalance call
manuelmauro Jul 12, 2024
476c31b
Revert last commit
manuelmauro Jul 12, 2024
f7b0763
Reduce the chance of executing an extra batch before killing the server
manuelmauro Jul 12, 2024
d5b414f
Modify MainNode config on the fly by changing properties in conf files
manuelmauro Jul 15, 2024
0392740
Fix aggregated block prove deadline value
manuelmauro Jul 15, 2024
e9d73fe
Modify the right config property (aggregated_block_execute_deadline)
manuelmauro Jul 15, 2024
aac7164
Remove block_reverter pre-building step in CLI
manuelmauro Jul 15, 2024
b206fce
Lint
manuelmauro Jul 15, 2024
909ee12
Merge branch 'main' into aon-add-revert-en-tests-zk-supervisor
manuelmauro Jul 16, 2024
50abe0c
refactor: do not pre-build external node
manuelmauro Jul 22, 2024
c3a5cec
Merge branch 'main' into aon-add-revert-en-tests-zk-supervisor
manuelmauro Jul 22, 2024
f810616
test: remove sleep after external node restart
manuelmauro Jul 22, 2024
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
4 changes: 4 additions & 0 deletions .github/workflows/ci-zk-toolbox-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ jobs:
run: |
ci_run zk_supervisor test revert --ignore-prerequisites --verbose

- name: Run revert tests (external node)
run: |
ci_run zk_supervisor test revert --external-node --ignore-prerequisites --verbose

- name: Show server.log logs
if: always()
run: ci_run cat server.log || true
Expand Down
162 changes: 101 additions & 61 deletions core/tests/revert-test/tests/revert-and-restart-en.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,76 @@
// main_contract.getTotalBatchesExecuted actually checks the number of batches executed.
import * as utils from 'utils';
import { Tester } from './tester';
import { exec, runServerInBackground, runExternalNodeInBackground } from './utils';
import * as zksync from 'zksync-ethers';
import * as ethers from 'ethers';
import { expect, assert } from 'chai';
import fs from 'fs';
import * as child_process from 'child_process';
import * as dotenv from 'dotenv';
import { getAllConfigsPath, loadConfig, shouldLoadConfigFromFile } from 'utils/build/file-configs';
import path from 'path';

const pathToHome = path.join(__dirname, '../../../..');
const fileConfig = shouldLoadConfigFromFile();

let mainEnv: string;
let extEnv: string;
if (process.env.DEPLOYMENT_MODE == 'Validium') {

let deploymentMode: string;

if (fileConfig.loadFromFile) {
const genesisConfig = loadConfig({ pathToHome, chain: fileConfig.chain, config: 'genesis.yaml' });
deploymentMode = genesisConfig.deploymentMode;
} else {
if (!process.env.DEPLOYMENT_MODE) {
throw new Error('DEPLOYMENT_MODE is not set');
}
if (!['Validium', 'Rollup'].includes(process.env.DEPLOYMENT_MODE)) {
throw new Error(`Unknown deployment mode: ${process.env.DEPLOYMENT_MODE}`);
}
deploymentMode = process.env.DEPLOYMENT_MODE;
}

if (deploymentMode == 'Validium') {
mainEnv = process.env.IN_DOCKER ? 'dev_validium_docker' : 'dev_validium';
extEnv = process.env.IN_DOCKER ? 'ext-node-validium-docker' : 'ext-node-validium';
} else if (process.env.DEPLOYMENT_MODE == 'Rollup') {
} else {
// Rollup deployment mode
mainEnv = process.env.IN_DOCKER ? 'docker' : 'dev';
extEnv = process.env.IN_DOCKER ? 'ext-node-docker' : 'ext-node';
} else {
throw new Error(`Unknown deployment mode: ${process.env.DEPLOYMENT_MODE}`);
}
const mainLogsPath: string = 'revert_main.log';
const extLogsPath: string = 'revert_ext.log';

let ethClientWeb3Url: string;
let apiWeb3JsonRpcHttpUrl: string;
let baseTokenAddress: string;
let enEthClientUrl: string;
let operatorAddress: string;

if (fileConfig.loadFromFile) {
const secretsConfig = loadConfig({ pathToHome, chain: fileConfig.chain, config: 'secrets.yaml' });
const generalConfig = loadConfig({ pathToHome, chain: fileConfig.chain, config: 'general.yaml' });
const contractsConfig = loadConfig({ pathToHome, chain: fileConfig.chain, config: 'contracts.yaml' });
const externalNodeConfig = loadConfig({ pathToHome, chain: fileConfig.chain, config: 'external_node.yaml' });
const walletsConfig = loadConfig({ pathToHome, chain: fileConfig.chain, config: 'wallets.yaml' });

ethClientWeb3Url = secretsConfig.l1.l1_rpc_url;
apiWeb3JsonRpcHttpUrl = generalConfig.api.web3_json_rpc.http_url;
baseTokenAddress = contractsConfig.l1.base_token_addr;
enEthClientUrl = externalNodeConfig.main_node_url;
operatorAddress = walletsConfig.operator.address;
} else {
let env = fetchEnv(mainEnv);
ethClientWeb3Url = env.ETH_CLIENT_WEB3_URL;
apiWeb3JsonRpcHttpUrl = env.API_WEB3_JSON_RPC_HTTP_URL;
baseTokenAddress = env.CONTRACTS_BASE_TOKEN_ADDR;
enEthClientUrl = `http://127.0.0.1:${env.EN_HTTP_PORT}`;
// TODO use env variable for this?
operatorAddress = '0xde03a0B5963f75f1C8485B355fF6D30f3093BDE7';
}

interface SuggestedValues {
lastExecutedL1BatchNumber: bigint;
nonce: number;
Expand All @@ -46,10 +95,6 @@ function parseSuggestedValues(jsonString: string): SuggestedValues {
};
}

function spawn(cmd: string, args: string[], options: child_process.SpawnOptions): child_process.ChildProcess {
return child_process.spawn(cmd, args, options);
}

function run(cmd: string, args: string[], options: child_process.SpawnOptions): child_process.SpawnSyncReturns<Buffer> {
let res = child_process.spawnSync(cmd, args, options);
expect(res.error).to.be.undefined;
Expand Down Expand Up @@ -79,18 +124,33 @@ function fetchEnv(zksyncEnv: string): any {
return { ...process.env, ...dotenv.parse(res.stdout) };
}

function runBlockReverter(args: string[]): string {
async function runBlockReverter(args: string[]): Promise<string> {
let env = fetchEnv(mainEnv);
env.RUST_LOG = 'off';
let res = run('./target/release/block_reverter', args, {

let fileConfigFlags = '';
if (fileConfig.loadFromFile) {
const configPaths = getAllConfigsPath({ pathToHome, chain: fileConfig.chain });
fileConfigFlags = `
--config-path=${configPaths['general.yaml']}
--contracts-config-path=${configPaths['contracts.yaml']}
--secrets-path=${configPaths['secrets.yaml']}
--wallets-path=${configPaths['wallets.yaml']}
--genesis-path=${configPaths['genesis.yaml']}
`;
}

const cmd = `cd ${pathToHome} && RUST_LOG=off cargo run --bin block_reverter --release -- ${args.join(
' '
)} ${fileConfigFlags}`;
const executedProcess = await exec(cmd, {
cwd: env.ZKSYNC_HOME,
env: {
...env,
PATH: process.env.PATH
}
});
console.log(res.stderr.toString());
return res.stdout.toString();

return executedProcess.stdout;
}

async function killServerAndWaitForShutdown(tester: Tester, server: string) {
Expand All @@ -112,7 +172,7 @@ async function killServerAndWaitForShutdown(tester: Tester, server: string) {
}

class MainNode {
constructor(public tester: Tester, private proc: child_process.ChildProcess) {}
constructor(public tester: Tester) {}

// Terminates all main node processes running.
public static async terminateAll() {
Expand All @@ -135,45 +195,38 @@ class MainNode {
env.ETH_SENDER_SENDER_AGGREGATED_BLOCK_EXECUTE_DEADLINE = enableExecute ? '1' : '10000';
// Set full mode for the Merkle tree as it is required to get blocks committed.
env.DATABASE_MERKLE_TREE_MODE = 'full';
console.log(`DATABASE_URL = ${env.DATABASE_URL}`);

let components = 'api,tree,eth,state_keeper,commitment_generator,da_dispatcher';
if (enableConsensus) {
components += ',consensus';
}

let proc = spawn('./target/release/zksync_server', ['--components', components], {
cwd: env.ZKSYNC_HOME,
runServerInBackground({
components: [components],
stdio: [null, logs, logs],
env: {
...env,
PATH: process.env.PATH
}
cwd: pathToHome,
env: env,
useZkInception: fileConfig.loadFromFile
});

// Wait until the main node starts responding.
let tester: Tester = await Tester.init(
env.ETH_CLIENT_WEB3_URL,
env.API_WEB3_JSON_RPC_HTTP_URL,
env.CONTRACTS_BASE_TOKEN_ADDR
);
let tester: Tester = await Tester.init(ethClientWeb3Url, apiWeb3JsonRpcHttpUrl, baseTokenAddress);
while (true) {
try {
await tester.syncWallet.provider.getBlockNumber();
break;
} catch (err) {
if (proc.exitCode != null) {
assert.fail(`server failed to start, exitCode = ${proc.exitCode}`);
}
// NOTE: not an infinite loop, as the test will eventually timeout.
console.log('waiting for api endpoint');
await utils.sleep(1);
}
}
return new MainNode(tester, proc);
return new MainNode(tester);
}
}

class ExtNode {
constructor(public tester: Tester, private proc: child_process.ChildProcess) {}
constructor(public tester: Tester) {}

// Terminates all main node processes running.
public static async terminateAll() {
Expand All @@ -188,51 +241,37 @@ class ExtNode {
// If enableConsensus is set, the node will use consensus P2P network to fetch blocks.
public static async spawn(logs: fs.WriteStream, enableConsensus: boolean): Promise<ExtNode> {
let env = fetchEnv(extEnv);
console.log(`DATABASE_URL = ${env.DATABASE_URL}`);
let args = [];
if (enableConsensus) {
args.push('--enable-consensus');
}
let proc = spawn('./target/release/zksync_external_node', args, {
cwd: env.ZKSYNC_HOME,

// Run server in background.
runExternalNodeInBackground({
stdio: [null, logs, logs],
env: {
...env,
PATH: process.env.PATH
}
cwd: pathToHome,
env: env,
useZkInception: fileConfig.loadFromFile
});

// Wait until the node starts responding.
let tester: Tester = await Tester.init(
env.EN_ETH_CLIENT_URL,
`http://127.0.0.1:${env.EN_HTTP_PORT}`,
env.CONTRACTS_BASE_TOKEN_ADDR
);
let tester: Tester = await Tester.init(ethClientWeb3Url, enEthClientUrl, baseTokenAddress);
while (true) {
try {
await tester.syncWallet.provider.getBlockNumber();
break;
} catch (err) {
if (proc.exitCode != null) {
assert.fail(`node failed to start, exitCode = ${proc.exitCode}`);
}
// NOTE: not an infinite loop, as the test will eventually timeout.
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved
console.log('waiting for api endpoint');
await utils.sleep(1);
}
}
return new ExtNode(tester, proc);
}

// Waits for the node process to exit.
public async waitForExit(): Promise<number> {
while (this.proc.exitCode === null) {
await utils.sleep(1);
}
return this.proc.exitCode;
return new ExtNode(tester);
}
}

describe('Block reverting test', function () {
if (process.env.SKIP_COMPILATION !== 'true') {
if (process.env.SKIP_COMPILATION !== 'true' && !fileConfig.loadFromFile) {
compileBinaries();
}
console.log(`PWD = ${process.env.PWD}`);
Expand Down Expand Up @@ -319,18 +358,18 @@ describe('Block reverting test', function () {
await killServerAndWaitForShutdown(mainNode.tester, 'zksync_server');

console.log('Ask block_reverter to suggest to which L1 batch we should revert');
const values_json = runBlockReverter([
const values_json = await runBlockReverter([
'print-suggested-values',
'--json',
'--operator-address',
'0xde03a0B5963f75f1C8485B355fF6D30f3093BDE7'
operatorAddress
]);
console.log(`values = ${values_json}`);
const values = parseSuggestedValues(values_json);
assert(lastExecuted === values.lastExecutedL1BatchNumber);

console.log('Send reverting transaction to L1');
runBlockReverter([
await runBlockReverter([
'send-eth-transaction',
'--l1-batch-number',
values.lastExecutedL1BatchNumber.toString(),
Expand All @@ -346,7 +385,7 @@ describe('Block reverting test', function () {
assert(lastCommitted2 === lastExecuted);

console.log('Rollback db');
runBlockReverter([
await runBlockReverter([
'rollback-db',
'--l1-batch-number',
values.lastExecutedL1BatchNumber.toString(),
Expand All @@ -359,10 +398,11 @@ describe('Block reverting test', function () {
mainNode = await MainNode.spawn(mainLogs, enableConsensus, true);

console.log('Wait for the external node to detect reorg and terminate');
await extNode.waitForExit();
await utils.sleep(3);
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved

console.log('Restart external node and wait for it to revert.');
extNode = await ExtNode.spawn(extLogs, enableConsensus);
await utils.sleep(30);
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved

console.log('Execute an L1 transaction');
const depositHandle = await extNode.tester.syncWallet.deposit({
Expand Down
81 changes: 81 additions & 0 deletions core/tests/revert-test/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { exec as _exec, spawn as _spawn, type ProcessEnvOptions } from 'child_process';
import { promisify } from 'util';

// executes a command in background and returns a child process handle
// by default pipes data to parent's stdio but this can be overridden
export function background({
command,
stdio = 'inherit',
cwd,
env
}: {
command: string;
stdio: any;
cwd?: ProcessEnvOptions['cwd'];
env?: ProcessEnvOptions['env'];
}) {
command = command.replace(/\n/g, ' ');
return _spawn(command, { stdio: stdio, shell: true, detached: true, cwd, env });
}

export function runInBackground({
command,
components,
stdio,
cwd,
env
}: {
command: string;
components?: string[];
stdio: any;
cwd?: Parameters<typeof background>[0]['cwd'];
env?: Parameters<typeof background>[0]['env'];
}) {
if (components && components.length > 0) {
command += ` --components=${components.join(',')}`;
}
background({ command, stdio, cwd, env });
}

export function runServerInBackground({
components,
stdio,
cwd,
env,
useZkInception
}: {
components?: string[];
stdio: any;
cwd?: Parameters<typeof background>[0]['cwd'];
env?: Parameters<typeof background>[0]['env'];
useZkInception?: boolean;
}) {
let command = useZkInception ? 'zk_inception server' : 'zk server';
runInBackground({ command, components, stdio, cwd, env });
}

export function runExternalNodeInBackground({
components,
stdio,
cwd,
env,
useZkInception
}: {
components?: string[];
stdio: any;
cwd?: Parameters<typeof background>[0]['cwd'];
env?: Parameters<typeof background>[0]['env'];
useZkInception?: boolean;
}) {
let command = useZkInception ? 'zk_inception external-node run' : 'zk external-node';
runInBackground({ command, components, stdio, cwd, env });
}

// async executor of shell commands
// spawns a new shell and can execute arbitrary commands, like "ls -la | grep .env"
// returns { stdout, stderr }
const promisified = promisify(_exec);
export function exec(command: string, options: ProcessEnvOptions) {
command = command.replace(/\n/g, ' ');
return promisified(command, options);
}
Loading
Loading