Skip to content

Commit 1e9f75b

Browse files
authored
fix: fix interop integration tests (#4300)
## What ❔ CI for interop integration tests broke recently as a result of the recent CI rework in: #4255. This PR defines a new (optional) secondary chain for integration tests, to be used for testing interop behavior, which defaults to the chain the tests are being run on. <!-- What are the changes this PR brings about? --> <!-- Example: This PR adds a PR template to the repo. --> <!-- (For bigger PRs adding more context is appreciated) --> ## Why ❔ <!-- Why are these changes done? What goal do they contribute to? What are the principles behind them? --> <!-- The `Why` has to be clear to non-Matter Labs entities running their own ZK Chain --> <!-- Example: PR templates ensure PR reviewers, observers, and future iterators are in context about the evolution of repos. --> ## Is this a breaking change? - [ ] Yes - [ ] No ## Operational changes <!-- Any config changes? Any new flags? Any changes to any scripts? --> <!-- Please add anything that non-Matter Labs entities running their own ZK Chain may need to know --> ## Checklist <!-- Check your PR fulfills the following items. --> <!-- For draft PRs check the boxes as you complete them. --> - [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`.
1 parent 9e77a87 commit 1e9f75b

File tree

15 files changed

+431
-190
lines changed

15 files changed

+431
-190
lines changed

core/tests/highlevel-test-tools/src/run-integration-tests.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,17 @@ async function runTest(
8080
}
8181
}
8282

83-
export async function runIntegrationTests(chainName: string, testPattern?: string): Promise<void> {
83+
export async function runIntegrationTests(
84+
chainName: string,
85+
secondChainName?: string | undefined,
86+
testPattern?: string
87+
): Promise<void> {
8488
await initTestWallet(chainName);
85-
await runTest('integration', chainName, testPattern, ['--verbose', '--ignore-prerequisites']);
89+
await runTest('integration', chainName, testPattern, [
90+
'--verbose',
91+
'--ignore-prerequisites',
92+
secondChainName ? `--second-chain=${secondChainName}` : ''
93+
]);
8694
}
8795

8896
export async function feesTest(chainName: string): Promise<void> {
@@ -110,7 +118,12 @@ export async function genesisRecoveryTest(chainName: string): Promise<void> {
110118
await runTest('recovery', chainName, undefined, ['--no-kill', '--ignore-prerequisites', '--verbose']);
111119
}
112120

113-
export async function enIntegrationTests(chainName: string): Promise<void> {
121+
export async function enIntegrationTests(chainName: string, secondChainName?: string | undefined): Promise<void> {
114122
await initTestWallet(chainName);
115-
await runTest('integration', chainName, undefined, ['--verbose', '--ignore-prerequisites', '--external-node']);
123+
await runTest('integration', chainName, undefined, [
124+
'--verbose',
125+
'--ignore-prerequisites',
126+
'--external-node',
127+
secondChainName ? `--second-chain=${secondChainName}` : ''
128+
]);
116129
}

core/tests/highlevel-test-tools/src/wait-for-batches.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ export async function waitForAllBatchesToBeExecuted(chainName: string, timeoutMs
4040
*/
4141
export async function generateRealisticLoad(chainName: string): Promise<void> {
4242
console.log(`🚀 Generating realistic load on chain: ${chainName}`);
43-
await runIntegrationTests(chainName, 'ETH token checks');
43+
await runIntegrationTests(chainName, undefined, 'ETH token checks');
4444
console.log(`✅ Realistic load generation completed for chain: ${chainName}`);
4545
}

core/tests/highlevel-test-tools/tests/en-integration-test.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { enIntegrationTests } from '../src/run-integration-tests';
55
describe('External Node Integration tests Test', () => {
66
it(`for ${TESTED_CHAIN_TYPE} chain`, async () => {
77
const testChain = await createChainAndStartServer(TESTED_CHAIN_TYPE, 'External Node Integration tests Test');
8+
// Define some chain B used for interop tests
9+
const secondChainType = TESTED_CHAIN_TYPE === 'era' ? 'validium' : 'era';
10+
const testSecondChain = await createChainAndStartServer(secondChainType, 'External Node Integration tests');
811

912
await testChain.generateRealisticLoad();
1013

@@ -14,6 +17,6 @@ describe('External Node Integration tests Test', () => {
1417

1518
await testChain.runExternalNode();
1619

17-
await enIntegrationTests(testChain.chainName);
20+
await enIntegrationTests(testChain.chainName, testSecondChain.chainName);
1821
});
1922
});

core/tests/highlevel-test-tools/tests/main-node-integration-test.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { TESTED_CHAIN_TYPE, createChainAndStartServer, runIntegrationTests } fro
44
describe('Integration Test', () => {
55
it(`for ${TESTED_CHAIN_TYPE} chain`, async () => {
66
const testChain = await createChainAndStartServer(TESTED_CHAIN_TYPE, 'Main Node Integration Test');
7+
// Define some chain B used for interop tests
8+
const secondChainType = TESTED_CHAIN_TYPE === 'era' ? 'validium' : 'era';
9+
const testSecondChain = await createChainAndStartServer(secondChainType, 'Main Node Integration Test');
710

8-
await runIntegrationTests(testChain.chainName);
11+
await runIntegrationTests(testChain.chainName, testSecondChain.chainName);
912
});
1013
});

core/tests/ts-integration/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as fs from 'fs';
33
export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = 800;
44

55
export const SYSTEM_UPGRADE_L2_TX_TYPE = 254;
6+
export const GATEWAY_CHAIN_ID = 505;
67
export const ADDRESS_ONE = '0x0000000000000000000000000000000000000001';
78
export const ETH_ADDRESS_IN_CONTRACTS = ADDRESS_ONE;
89
export const L1_TO_L2_ALIAS_OFFSET = '0x1111000000000000000000000000000000001111';

core/tests/ts-integration/src/context-owner.ts

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ export const L1_DEFAULT_ETH_PER_ACCOUNT = ethers.parseEther('0.08');
2020
// Stress tests for L1->L2 transactions on localhost require a lot of upfront payment, but these are skipped during tests on normal environments
2121
export const L1_EXTENDED_TESTS_ETH_PER_ACCOUNT = ethers.parseEther('0.5');
2222
export const L2_DEFAULT_ETH_PER_ACCOUNT = ethers.parseEther('0.5');
23+
export const L2_SECOND_CHAIN_ETH_PER_ACCOUNT = ethers.parseEther('0.05');
2324

2425
// Stress tests on local host may require a lot of additiomal funds, but these are skipped during tests on normal environments
2526
export const L2_EXTENDED_TESTS_ETH_PER_ACCOUNT = ethers.parseEther('50');
27+
export const L2_SECOND_CHAIN_EXTENDED_TESTS_ETH_PER_ACCOUNT = ethers.parseEther('5');
2628
export const ERC20_PER_ACCOUNT = ethers.parseEther('10000.0');
2729

2830
interface VmPlaygroundHealth {
@@ -79,9 +81,11 @@ export class TestContextOwner {
7981

8082
private mainEthersWallet: ethers.Wallet;
8183
private mainSyncWallet: zksync.Wallet;
84+
private secondChainMainSyncWallet: zksync.Wallet | undefined;
8285

8386
private l1Provider: ethers.JsonRpcProvider;
8487
private l2Provider: zksync.Provider;
88+
private l2ProviderSecondChain: zksync.Provider | undefined;
8589

8690
private reporter: Reporter = new Reporter();
8791

@@ -109,6 +113,28 @@ export class TestContextOwner {
109113

110114
this.mainEthersWallet = new ethers.Wallet(env.mainWalletPK, this.l1Provider);
111115
this.mainSyncWallet = new zksync.Wallet(env.mainWalletPK, this.l2Provider, this.l1Provider);
116+
117+
if (env.l2ChainIdSecondChain) {
118+
this.reporter.message('Using second chain L2 provider: ' + env.l2NodeUrlSecondChain);
119+
this.l2ProviderSecondChain = new RetryProvider(
120+
{
121+
url: env.l2NodeUrlSecondChain!,
122+
timeout: 1200 * 1000
123+
},
124+
undefined,
125+
this.reporter
126+
);
127+
128+
if (isLocalHost(env.network)) {
129+
this.l2ProviderSecondChain.pollingInterval = 100;
130+
}
131+
132+
this.secondChainMainSyncWallet = new zksync.Wallet(
133+
env.mainWalletPK,
134+
this.l2ProviderSecondChain,
135+
this.l1Provider
136+
);
137+
}
112138
}
113139

114140
// Returns the required amount of L1 ETH
@@ -121,6 +147,13 @@ export class TestContextOwner {
121147
return isLocalHost(this.env.network) ? L2_EXTENDED_TESTS_ETH_PER_ACCOUNT : L2_DEFAULT_ETH_PER_ACCOUNT;
122148
}
123149

150+
// Returns the required amount of L2 ETH for the second chain
151+
requiredL2ETHPerAccountSecondChain() {
152+
return isLocalHost(this.env.network)
153+
? L2_SECOND_CHAIN_EXTENDED_TESTS_ETH_PER_ACCOUNT
154+
: L2_SECOND_CHAIN_ETH_PER_ACCOUNT;
155+
}
156+
124157
/**
125158
* Performs the test context initialization.
126159
*
@@ -228,14 +261,20 @@ export class TestContextOwner {
228261
// `+ 1 for the main account (it has to send all these transactions).
229262
const accountsAmount = BigInt(suites.length) + 1n;
230263

231-
const l2ETHAmountToDeposit = await this.ensureBalances(accountsAmount);
264+
const { l2ETHAmountToDeposit, l2ETHAmountToDepositSecondChain } = await this.ensureBalances(accountsAmount);
232265
const l2ERC20AmountToDeposit = ERC20_PER_ACCOUNT * accountsAmount;
233266
const wallets = this.createTestWallets(suites);
234267
const bridgehubContract = await this.mainSyncWallet.getBridgehubContract();
235268
const baseTokenAddress = await bridgehubContract.baseToken(this.env.l2ChainId);
236269
await this.distributeL1BaseToken(wallets, l2ERC20AmountToDeposit, baseTokenAddress);
237270
await this.cancelAllowances();
238-
await this.distributeL1Tokens(wallets, l2ETHAmountToDeposit, l2ERC20AmountToDeposit, baseTokenAddress);
271+
await this.distributeL1Tokens(
272+
wallets,
273+
l2ETHAmountToDeposit,
274+
l2ETHAmountToDepositSecondChain,
275+
l2ERC20AmountToDeposit,
276+
baseTokenAddress
277+
);
239278
await this.distributeL2Tokens(wallets);
240279

241280
this.reporter.finishAction();
@@ -245,7 +284,9 @@ export class TestContextOwner {
245284
/**
246285
* Checks the operator account balances on L1 and L2 and deposits funds if required.
247286
*/
248-
private async ensureBalances(accountsAmount: bigint): Promise<bigint> {
287+
private async ensureBalances(
288+
accountsAmount: bigint
289+
): Promise<{ l2ETHAmountToDeposit: bigint; l2ETHAmountToDepositSecondChain: bigint }> {
249290
this.reporter.startAction(`Checking main account balance`);
250291

251292
this.reporter.message(`Operator address is ${this.mainEthersWallet.address}`);
@@ -254,11 +295,26 @@ export class TestContextOwner {
254295
const actualL2ETHAmount = await this.mainSyncWallet.getBalance();
255296
this.reporter.message(`Operator balance on L2 is ${ethers.formatEther(actualL2ETHAmount)} ETH`);
256297

298+
let l2ETHAmountToDepositSecondChain = 0n;
299+
if (this.env.l2ChainIdSecondChain) {
300+
// We only need enough funds for a single test suite
301+
const requiredL2SecondChainETHAmount = this.requiredL2ETHPerAccountSecondChain() * accountsAmount;
302+
const actualL2SecondChainETHAmount = await this.secondChainMainSyncWallet!.getBalance();
303+
this.reporter.message(
304+
`Operator balance on second chain is ${ethers.formatEther(actualL2SecondChainETHAmount)} ETH`
305+
);
306+
if (requiredL2SecondChainETHAmount > actualL2SecondChainETHAmount) {
307+
l2ETHAmountToDepositSecondChain = requiredL2SecondChainETHAmount - actualL2SecondChainETHAmount;
308+
}
309+
}
310+
257311
// We may have enough funds in L2. If that's the case, no need to deposit more than required.
258312
const l2ETHAmountToDeposit =
259313
requiredL2ETHAmount > actualL2ETHAmount ? requiredL2ETHAmount - actualL2ETHAmount : 0n;
260314

261-
const requiredL1ETHAmount = this.requiredL1ETHPerAccount() * accountsAmount + l2ETHAmountToDeposit;
315+
const requiredL1ETHAmount =
316+
this.requiredL1ETHPerAccount() * accountsAmount + l2ETHAmountToDeposit + l2ETHAmountToDepositSecondChain;
317+
// Both mainSyncWallet and secondChainMainSyncWallet share the same L1 wallet
262318
const actualL1ETHAmount = await this.mainSyncWallet.getBalanceL1();
263319
this.reporter.message(`Operator balance on L1 is ${ethers.formatEther(actualL1ETHAmount)} ETH`);
264320

@@ -270,7 +326,7 @@ export class TestContextOwner {
270326
}
271327
this.reporter.finishAction();
272328

273-
return l2ETHAmountToDeposit;
329+
return { l2ETHAmountToDeposit, l2ETHAmountToDepositSecondChain };
274330
}
275331

276332
/**
@@ -386,6 +442,7 @@ export class TestContextOwner {
386442
private async distributeL1Tokens(
387443
wallets: TestWallets,
388444
l2ETHAmountToDeposit: bigint,
445+
l2ETHAmountToDepositSecondChain: bigint,
389446
l2erc20DepositAmount: bigint,
390447
baseTokenAddress: zksync.types.Address
391448
) {
@@ -430,6 +487,30 @@ export class TestContextOwner {
430487
);
431488
await depositHandle;
432489
}
490+
491+
// Deposit L2 tokens on the second chain (if needed).
492+
if (l2ETHAmountToDepositSecondChain != 0n) {
493+
// Given that we've already sent a number of transactions,
494+
// we have to correctly send nonce.
495+
const depositHandle = this.secondChainMainSyncWallet!.deposit({
496+
token: zksync.utils.ETH_ADDRESS,
497+
amount: l2ETHAmountToDepositSecondChain as BigNumberish,
498+
overrides: {
499+
nonce,
500+
gasPrice
501+
}
502+
}).then((tx) => {
503+
const amount = ethers.formatEther(l2ETHAmountToDeposit);
504+
this.reporter.debug(
505+
`Sent ETH deposit on second chain. Nonce ${tx.nonce}, amount: ${amount}, hash: ${tx.hash}`
506+
);
507+
return tx.wait();
508+
});
509+
nonce = nonce + 1;
510+
this.reporter.debug(`Nonce changed by 1 for ETH deposit on second chain, new nonce: ${nonce}`);
511+
await depositHandle;
512+
}
513+
433514
// Define values for handling ERC20 transfers/deposits.
434515
const erc20Token = this.env.erc20Token.l1Address;
435516
const erc20MintAmount = l2erc20DepositAmount * 100n;
@@ -546,6 +627,21 @@ export class TestContextOwner {
546627
);
547628
l2startNonce += l2TxPromises.length;
548629

630+
let l2TxPromisesSecondChain: Promise<any>[] = [];
631+
if (this.env.l2ChainIdSecondChain) {
632+
const l2startNonceSecondChain = await this.secondChainMainSyncWallet!.getNonce();
633+
// ETH transfers on second chain.
634+
l2TxPromisesSecondChain = await sendTransfers(
635+
zksync.utils.ETH_ADDRESS,
636+
this.secondChainMainSyncWallet!,
637+
wallets,
638+
this.requiredL2ETHPerAccountSecondChain(),
639+
l2startNonceSecondChain,
640+
undefined,
641+
this.reporter
642+
);
643+
}
644+
549645
// ERC20 transfers.
550646
const l2TokenAddress = await this.mainSyncWallet.l2TokenAddress(this.env.erc20Token.l1Address);
551647
const erc20Promises = await sendTransfers(
@@ -557,7 +653,7 @@ export class TestContextOwner {
557653
undefined,
558654
this.reporter
559655
);
560-
l2TxPromises.push(...erc20Promises);
656+
l2TxPromises.push(...l2TxPromisesSecondChain, ...erc20Promises);
561657
await Promise.all(l2TxPromises);
562658

563659
this.reporter.finishAction();
@@ -659,6 +755,7 @@ export class TestContextOwner {
659755
}
660756
try {
661757
this.l2Provider.destroy();
758+
this.l2ProviderSecondChain?.destroy();
662759
} catch (err: any) {
663760
// Catch any request cancellation errors that propagate here after destroying L2 provider
664761
console.log(`Caught error while destroying L2 provider: ${err}`);
@@ -677,6 +774,11 @@ export class TestContextOwner {
677774
await killPidWithAllChilds(this.env.l2NodePid, 9);
678775
this.reporter.finishAction();
679776
}
777+
if (this.env.l2NodePidSecondChain !== undefined) {
778+
this.reporter.startAction(`Terminating L2 node process`);
779+
await killPidWithAllChilds(this.env.l2NodePidSecondChain, 9);
780+
this.reporter.finishAction();
781+
}
680782
}
681783

682784
/**

0 commit comments

Comments
 (0)