Skip to content

Commit

Permalink
Merge pull request #612 from EYBlockchain/westlad/enhanced-gas-test
Browse files Browse the repository at this point in the history
Westlad/enhanced gas test
  • Loading branch information
druiz0992 authored Apr 29, 2022
2 parents faaa2fd + 49baf18 commit 0a16fcc
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 15 deletions.
34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
```

Expand Down
2 changes: 1 addition & 1 deletion nightfall-deployer/src/circuit-setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
134 changes: 127 additions & 7 deletions test/e2e/gas.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ 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,
expectTransaction,
} from '../utils.mjs';

// so we can use require with mjs file
const { expect } = chai;
Expand All @@ -22,8 +28,8 @@ const {
signingKeys,
} = config.TEST_OPTIONS;

const txPerBlock = 32;
const expectedGasCostPerTx = 10000 * txPerBlock;
const txPerBlock = process.env.TRANSACTIONS_PER_BLOCK || 32;
const expectedGasCostPerTx = 100000 + 15000 * txPerBlock;
const nf3Users = [new Nf3(signingKeys.user1, environment), new Nf3(signingKeys.user2, environment)];
const nf3Proposer1 = new Nf3(signingKeys.proposer1, environment);

Expand All @@ -33,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
Expand Down Expand Up @@ -75,12 +84,14 @@ 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}`,
`Block proposal gas used was ${gasUsed}, gas used per transaction was ${
gasUsed / txPerBlock
}`,
);
gasCost = gasUsed;
console.log(gasCost);
});

await nf3Users[0].init(mnemonics.user1);
Expand All @@ -91,7 +102,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],
Expand All @@ -106,6 +120,112 @@ describe('Gas test', () => {

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.
const receipts = await depositNTransactions(
nf3Users[0],
txPerBlock,
erc20Address,
tokenType,
transferValue,
tokenId,
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.
const receipts = 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);
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.
const receipts = 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);
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.
const receipts = 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);
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();
}
});
});

after(async () => {
Expand Down
54 changes: 54 additions & 0 deletions test/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0a16fcc

Please sign in to comment.