From 04885b629701d95cb88727fe9bda617d4449ed6c Mon Sep 17 00:00:00 2001 From: Westlad Date: Thu, 7 Apr 2022 17:02:26 +0100 Subject: [PATCH 1/3] feat: gas test meaures all four transaction types --- test/e2e/gas.test.mjs | 92 ++++++++++++++++++++++++++++++++++++++++--- test/utils.mjs | 54 +++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 6 deletions(-) diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index 08c23d8a5..d7705d42d 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -4,7 +4,12 @@ import chaiHttp from 'chai-http'; import config from 'config'; import chaiAsPromised from 'chai-as-promised'; import Nf3 from '../../cli/lib/nf3.mjs'; -import { depositNTransactions, Web3Client } from '../utils.mjs'; +import { + depositNTransactions, + transferNTransactions, + withdrawNTransactions, + Web3Client, +} from '../utils.mjs'; // so we can use require with mjs file const { expect } = chai; @@ -22,8 +27,8 @@ const { signingKeys, } = config.TEST_OPTIONS; -const txPerBlock = 32; -const expectedGasCostPerTx = 10000 * txPerBlock; +const txPerBlock = process.env.TRANSACTIONS_PER_BLOCK || 32; +const expectedGasCostPerTx = 20000 * txPerBlock; const nf3Users = [new Nf3(signingKeys.user1, environment), new Nf3(signingKeys.user2, environment)]; const nf3Proposer1 = new Nf3(signingKeys.proposer1, environment); @@ -75,12 +80,12 @@ describe('Gas test', () => { // Proposer listening for incoming events const newGasBlockEmitter = await nf3Proposer1.startProposer(); - newGasBlockEmitter.on('gascost', async gasUsed => { + newGasBlockEmitter.on('receipt', async receipt => { + const { gasUsed } = receipt; console.log( `Block proposal gas cost was ${gasUsed}, cost per transaction was ${gasUsed / txPerBlock}`, ); gasCost = gasUsed; - console.log(gasCost); }); await nf3Users[0].init(mnemonics.user1); @@ -91,7 +96,10 @@ describe('Gas test', () => { }); describe('Deposits', () => { - it('should get a reasonable amount of gas cost', async function () { + // we need more deposits because we won't have enough input transactions until + // after this block is made, by which time it's too late. + // also,the first block costs more due to one-off setup costs. + it('should make extra deposits so that we can double-transfer', async function () { // We create enough transactions to fill blocks full of deposits. await depositNTransactions( nf3Users[0], @@ -104,6 +112,78 @@ describe('Gas test', () => { ); eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + }); + it('should be a reasonable gas cost', async function () { + // We create enough transactions to fill blocks full of deposits. + await depositNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue, + tokenId, + fee, + ); + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + }); + }); + + describe('Single transfers', () => { + it('should be a reasonable gas cost', async function () { + // We create enough transactions to fill blocks full of deposits. + await transferNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue, + tokenId, + nf3Users[0].zkpKeys.compressedPkd, + fee, + ); + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + }); + }); + + describe('Double transfers', () => { + it('should be a reasonable gas cost', async function () { + // We create enough transactions to fill blocks full of deposits. + await transferNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue / 2, + tokenId, + nf3Users[0].zkpKeys.compressedPkd, + fee, + ); + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + }); + }); + + describe('Withdraws', () => { + it('should be a reasonable gas cost', async function () { + // We create enough transactions to fill blocks full of deposits. + await withdrawNTransactions( + nf3Users[0], + txPerBlock, + erc20Address, + tokenType, + transferValue / 2, + tokenId, + nf3Users[0].ethereumAddress, + fee, + ); + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + expect(gasCost).to.be.lessThan(expectedGasCostPerTx); }); }); diff --git a/test/utils.mjs b/test/utils.mjs index d849f4b09..e90ac02f9 100644 --- a/test/utils.mjs +++ b/test/utils.mjs @@ -346,6 +346,60 @@ export const depositNTransactions = async (nf3, N, ercAddress, tokenType, value, return depositTransactions; }; +export const transferNTransactions = async ( + nf3, + N, + ercAddress, + tokenType, + value, + tokenId, + compressedPkd, + fee, +) => { + const transferTransactions = []; + for (let i = 0; i < N; i++) { + const res = await nf3.transfer( + false, + ercAddress, + tokenType, + value, + tokenId, + compressedPkd, + fee, + ); + expectTransaction(res); + transferTransactions.push(res); + } + return transferTransactions; +}; + +export const withdrawNTransactions = async ( + nf3, + N, + ercAddress, + tokenType, + value, + tokenId, + recipientAddress, + fee, +) => { + const withdrawTransactions = []; + for (let i = 0; i < N; i++) { + const res = await nf3.withdraw( + false, + ercAddress, + tokenType, + value, + tokenId, + recipientAddress, + fee, + ); + expectTransaction(res); + withdrawTransactions.push(res); + } + return withdrawTransactions; +}; + /** function to retrieve balance of user because getLayer2Balances returns balances of all users From eb1b26bdc507c854f068d2bef9e8027a0974710c Mon Sep 17 00:00:00 2001 From: Westlad Date: Fri, 8 Apr 2022 11:14:15 +0100 Subject: [PATCH 2/3] feat: include relevant L1 gas costs --- nightfall-deployer/src/circuit-setup.mjs | 2 +- test/e2e/gas.test.mjs | 60 ++++++++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/nightfall-deployer/src/circuit-setup.mjs b/nightfall-deployer/src/circuit-setup.mjs index cf33a3366..3139cd85e 100644 --- a/nightfall-deployer/src/circuit-setup.mjs +++ b/nightfall-deployer/src/circuit-setup.mjs @@ -27,7 +27,7 @@ async function waitForZokrates() { 200 ) { logger.warn( - `No response from zokratesworker yet. That's ok. We'll wait three seconds and try again...`, + `No response from zokrates_worker yet. That's ok. We'll wait three seconds and try again...`, ); await new Promise(resolve => setTimeout(resolve, 3000)); diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index d7705d42d..080635117 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -9,6 +9,7 @@ import { transferNTransactions, withdrawNTransactions, Web3Client, + expectTransaction, } from '../utils.mjs'; // so we can use require with mjs file @@ -28,7 +29,7 @@ const { } = config.TEST_OPTIONS; const txPerBlock = process.env.TRANSACTIONS_PER_BLOCK || 32; -const expectedGasCostPerTx = 20000 * txPerBlock; +const expectedGasCostPerTx = 100000 + 15000 * txPerBlock; const nf3Users = [new Nf3(signingKeys.user1, environment), new Nf3(signingKeys.user2, environment)]; const nf3Proposer1 = new Nf3(signingKeys.proposer1, environment); @@ -38,6 +39,9 @@ let erc20Address; let stateAddress; let eventLogs = []; +const averageL1GasCost = receipts => + receipts.map(receipt => receipt.gasUsed).reduce((acc, el) => acc + el) / receipts.length; + /* This function tries to zero the number of unprocessed transactions in the optimist node that nf3 is connected to. We call it extensively on the tests, as we want to query stuff from the @@ -83,7 +87,9 @@ describe('Gas test', () => { newGasBlockEmitter.on('receipt', async receipt => { const { gasUsed } = receipt; console.log( - `Block proposal gas cost was ${gasUsed}, cost per transaction was ${gasUsed / txPerBlock}`, + `Block proposal gas used was ${gasUsed}, gas used per transaction was ${ + gasUsed / txPerBlock + }`, ); gasCost = gasUsed; }); @@ -116,7 +122,7 @@ describe('Gas test', () => { }); it('should be a reasonable gas cost', async function () { // We create enough transactions to fill blocks full of deposits. - await depositNTransactions( + const receipts = await depositNTransactions( nf3Users[0], txPerBlock, erc20Address, @@ -126,15 +132,15 @@ describe('Gas test', () => { fee, ); eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); - expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + console.log('Deposit L1 average gas used was', averageL1GasCost(receipts)); }); }); describe('Single transfers', () => { it('should be a reasonable gas cost', async function () { // We create enough transactions to fill blocks full of deposits. - await transferNTransactions( + const receipts = await transferNTransactions( nf3Users[0], txPerBlock, erc20Address, @@ -145,15 +151,18 @@ describe('Gas test', () => { fee, ); eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); - expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + console.log( + 'Single transfer L1 average gas used, if on-chain, was', + averageL1GasCost(receipts), + ); }); }); describe('Double transfers', () => { it('should be a reasonable gas cost', async function () { // We create enough transactions to fill blocks full of deposits. - await transferNTransactions( + const receipts = await transferNTransactions( nf3Users[0], txPerBlock, erc20Address, @@ -164,15 +173,18 @@ describe('Gas test', () => { fee, ); eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); - expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + console.log( + 'Double transfer L1 average gas used, if on-chain, was', + averageL1GasCost(receipts), + ); }); }); describe('Withdraws', () => { it('should be a reasonable gas cost', async function () { // We create enough transactions to fill blocks full of deposits. - await withdrawNTransactions( + const receipts = await withdrawNTransactions( nf3Users[0], txPerBlock, erc20Address, @@ -183,8 +195,36 @@ describe('Gas test', () => { fee, ); eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); - expect(gasCost).to.be.lessThan(expectedGasCostPerTx); + console.log('Withdraw L1 average gas used, if on-chain, was', averageL1GasCost(receipts)); + }); + }); + + describe('Finalise withdraws', () => { + it('should withdraw from L2, checking for L1 balance (only with time-jump client)', async function () { + const nodeInfo = await web3Client.getInfo(); + if (nodeInfo.includes('TestRPC')) { + const startBalance = await web3Client.getBalance(nf3Users[0].ethereumAddress); + const withdrawal = await nf3Users[0].getLatestWithdrawHash(); + await emptyL2(nf3Users[0]); + await web3Client.timeJump(3600 * 24 * 10); // jump in time by 50 days + const commitments = await nf3Users[0].getPendingWithdraws(); + expect( + commitments[nf3Users[0].zkpKeys.compressedPkd][erc20Address].length, + ).to.be.greaterThan(0); + expect( + commitments[nf3Users[0].zkpKeys.compressedPkd][erc20Address].filter(c => c.valid === true) + .length, + ).to.be.greaterThan(0); + const res = await nf3Users[0].finaliseWithdrawal(withdrawal); + expectTransaction(res); + const endBalance = await web3Client.getBalance(nf3Users[0].ethereumAddress); + expect(parseInt(endBalance, 10)).to.be.lessThan(parseInt(startBalance, 10)); + console.log('The gas used for finalise withdraw, back to L1, was', res.gasUsed); + } else { + console.log(' Not using a time-jump capable test client so this test is skipped'); + this.skip(); + } }); }); From 49baf18e233cfa258d2ab465bae635e4b925cd34 Mon Sep 17 00:00:00 2001 From: Westlad Date: Fri, 8 Apr 2022 13:11:47 +0100 Subject: [PATCH 3/3] docs: update readme gas test section --- README.md | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f4b9edfbd..5fdde7fc3 100644 --- a/README.md +++ b/README.md @@ -108,17 +108,37 @@ make the standard tests fast. In reality, a value of two transactions per block, although convenient for testing, wouldn't make very efficient use of Optimism. A more realistic value is 32 transactions per layer 2 block. This -value can be configured by the environment variable `TRANSACTIONS_PER_BLOCK` in the -`docker-compose.yml` file (part of the `optimist` service). This is important for the Block Gas -measurement, which requires a value of 32 to be set. +value can be configured by the environment variable `TRANSACTIONS_PER_BLOCK`. This is important +for the Block Gas measurement, which only makes sense for more realistic block sizes. -To measure the Block Gas used per transaction, first edit the `TRANSACTIONS_PER_BLOCK` variable as -above (don't forget to change it back after), restart nightfall_3, and run: +To measure the Block Gas used per transaction, export the `TRANSACTIONS_PER_BLOCK` variable in both +the terminal which will run nightfall and the terminal from which the test will be run, setting it to the +value at which you want to run the test (32 in the example here): + +```sh +export TRANSACTIONS_PER_BLOCK=32 +``` +Any reasonable value will work but they must be the same for both nightfall and the test. +Obviously, it only makes sense to compare performance at the same value of `TRANSACTIONS_PER_BLOCK`. + +Then start nightfall: + +```sh +./start-nightfall -g -d -s +``` +Then, in the other terminal window run the test ```sh npm run test-gas ``` +The test will print out values of gas used for each type of transaction as it progresses. +Do not forget to set the `TRANSACTIONS_PER_BLOCK` back to 2 when you have finished or the other tests +may fail in strange ways. + +```sh +export TRANSACTIONS_PER_BLOCK=2 +``` ### Test chain reorganisations In Layer 2 solutions, Layer 2 state is held off-chain but created by a series of Layer 1 @@ -192,14 +212,14 @@ Nightfall_3 provides a Wallet to exercise its features. To use it: - Deploy nightfall (only ganache for now) from Nightfall's root folder -``` +```sh ./start-nightfall -g -d -s ``` - In a different terminal, start proposer from Nightfall's root folder once Nightfall deployment is finished (you will see this `nightfall_3-deployer-1 exited with code 0`). -``` +```sh ./proposer ```