Skip to content

Commit

Permalink
feat: benchmark tx size with fee (#5414)
Browse files Browse the repository at this point in the history
This PR adds new benchmarks to compare tx size with different fee
payment methods.

The tx type chosen for this benchmark is a simple private transfer:
`token_contract.transfer(alice, bob, 1n, 0)` . This is expected to (1)
nullify one of Alice's note (2) create a note for Bob with the amount
and (3) create a note for Alice with the left over from the nullified
note. On top of this base we add the costs of paying the fee in
public/private

Fix #5403
  • Loading branch information
alexghr authored Mar 26, 2024
1 parent 3450e24 commit 543f8a2
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 11 deletions.
12 changes: 12 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,16 @@ jobs:
aztec_manifest_key: end-to-end
<<: *defaults_e2e_test

bench-tx-size:
steps:
- *checkout
- *setup_env
- run:
name: "Benchmark"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose-no-sandbox.yml TEST=benchmarks/bench_tx_size_fees.test.ts ENABLE_GAS=1 DEBUG=aztec:benchmarks:*,aztec:sequencer,aztec:sequencer:*,aztec:world_state,aztec:merkle_trees
aztec_manifest_key: end-to-end
<<: *defaults_e2e_test

build-docs:
machine:
image: default
Expand Down Expand Up @@ -1551,10 +1561,12 @@ workflows:
# Benchmark jobs.
- bench-publish-rollup: *e2e_test
- bench-process-history: *e2e_test
- bench-tx-size: *e2e_test
- bench-summary:
requires:
- bench-publish-rollup
- bench-process-history
- bench-tx-size
<<: *defaults

# Production releases.
Expand Down
5 changes: 0 additions & 5 deletions yarn-project/aztec.js/src/fee/native_fee_payment_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ export class NativeFeePaymentMethod implements FeePaymentMethod {
*/
getFunctionCalls(feeLimit: Fr): Promise<FunctionCall[]> {
return Promise.resolve([
{
to: this.#gasTokenAddress,
functionData: new FunctionData(FunctionSelector.fromSignature('check_balance(Field)'), false),
args: [feeLimit],
},
{
to: this.#gasTokenAddress,
functionData: new FunctionData(FunctionSelector.fromSignature('pay_fee(Field)'), false),
Expand Down
9 changes: 8 additions & 1 deletion yarn-project/circuit-types/src/stats/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export type MetricGroupBy =
| 'circuit-name'
| 'classes-registered'
| 'leaf-count'
| 'data-writes';
| 'data-writes'
| 'fee-payment-method';

/** Definition of a metric to track in benchmarks. */
export interface Metric {
Expand Down Expand Up @@ -133,6 +134,12 @@ export const Metrics = [
description: 'Size of txs received in the mempool.',
events: ['tx-added-to-pool'],
},
{
name: 'tx_with_fee_size_in_bytes',
groupBy: 'fee-payment-method',
description: 'Size of txs after fully processing them (including fee payment).',
events: ['tx-added-to-pool'],
},
{
name: 'tx_pxe_processing_time_ms',
groupBy: 'data-writes',
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/circuit-types/src/stats/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ export type TxStats = {
newNullifierCount: number;
/** How many classes were registered through the canonical class registerer. */
classRegisteredCount: number;
/** How this tx pays for its fee */
feePaymentMethod: 'none' | 'native' | 'fpc_public' | 'fpc_private';
};

/**
Expand All @@ -168,7 +170,8 @@ export type TxSequencerProcessingStats = {
duration: number;
/** Count of how many public writes this tx has made. Acts as a proxy for how 'heavy' this tx */
publicDataUpdateRequests: number;
} & TxStats;
effectsSize: number;
} & Pick<TxStats, 'classRegisteredCount' | 'newCommitmentCount' | 'feePaymentMethod'>;

/**
* Stats for tree insertions
Expand Down
12 changes: 12 additions & 0 deletions yarn-project/circuit-types/src/tx/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ export class Tx {

proofSize: this.proof.buffer.length,
size: this.toBuffer().length,

feePaymentMethod:
// needsTeardown? then we pay a fee
this.data.needsTeardown
? // needsSetup? then we pay through a fee payment contract
this.data.needsSetup
? // if the first call is to `approve_public_authwit`, then it's a public payment
this.enqueuedPublicFunctionCalls.at(-1)!.functionData.selector.toField().toBigInt() === 0x43417bb1n
? 'fpc_public'
: 'fpc_private'
: 'native'
: 'none',
classRegisteredCount: this.unencryptedLogs
.unrollLogs()
.map(log => UnencryptedL2Log.fromBuffer(log))
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/scripts/docker-compose-no-sandbox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ services:
WS_BLOCK_CHECK_INTERVAL_MS: 50
PXE_BLOCK_POLLING_INTERVAL_MS: 50
ARCHIVER_VIEM_POLLING_INTERVAL_MS: 500
ENABLE_GAS: ${ENABLE_GAS:-''}
JOB_NAME: ${JOB_NAME:-''}
command: ${TEST:-./src/e2e_deploy_contract.test.ts}
volumes:
- ../log:/usr/src/yarn-project/end-to-end/log:rw
Expand Down
84 changes: 84 additions & 0 deletions yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
AccountWalletWithPrivateKey,
AztecAddress,
FeePaymentMethod,
NativeFeePaymentMethod,
PrivateFeePaymentMethod,
PublicFeePaymentMethod,
TxStatus,
} from '@aztec/aztec.js';
import { FPCContract, GasTokenContract, TokenContract } from '@aztec/noir-contracts.js';
import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token';

import { jest } from '@jest/globals';

import { EndToEndContext, publicDeployAccounts, setup } from '../fixtures/utils.js';

jest.setTimeout(50_000);

describe('benchmarks/tx_size_fees', () => {
let ctx: EndToEndContext;
let aliceWallet: AccountWalletWithPrivateKey;
let bobAddress: AztecAddress;
let sequencerAddress: AztecAddress;
let gas: GasTokenContract;
let fpc: FPCContract;
let token: TokenContract;

// setup the environment
beforeAll(async () => {
ctx = await setup(3);
aliceWallet = ctx.wallets[0];
bobAddress = ctx.wallets[1].getAddress();
sequencerAddress = ctx.wallets[2].getAddress();

await ctx.aztecNode.setConfig({
feeRecipient: sequencerAddress,
});

await publicDeployAccounts(aliceWallet, ctx.accounts);
});

// deploy the contracts
beforeAll(async () => {
gas = await GasTokenContract.at(
getCanonicalGasTokenAddress(ctx.deployL1ContractsValues.l1ContractAddresses.gasPortalAddress),
aliceWallet,
);
token = await TokenContract.deploy(aliceWallet, aliceWallet.getAddress(), 'test', 'test', 18).send().deployed();
fpc = await FPCContract.deploy(aliceWallet, token.address, gas.address).send().deployed();
});

// mint tokens
beforeAll(async () => {
await Promise.all([
gas.methods.mint_public(aliceWallet.getAddress(), 1000n).send().wait(),
token.methods.privately_mint_private_note(1000n).send().wait(),
token.methods.mint_public(aliceWallet.getAddress(), 1000n).send().wait(),

gas.methods.mint_public(fpc.address, 1000n).send().wait(),
]);
});

it.each<() => Promise<FeePaymentMethod | undefined>>([
() => Promise.resolve(undefined),
() => NativeFeePaymentMethod.create(aliceWallet),
() => Promise.resolve(new PublicFeePaymentMethod(token.address, fpc.address, aliceWallet)),
() => Promise.resolve(new PrivateFeePaymentMethod(token.address, fpc.address, aliceWallet)),
])('sends a tx with a fee', async createPaymentMethod => {
const paymentMethod = await createPaymentMethod();
const tx = await token.methods
.transfer(aliceWallet.getAddress(), bobAddress, 1n, 0)
.send({
fee: paymentMethod
? {
maxFee: 3n,
paymentMethod,
}
: undefined,
})
.wait();

expect(tx.status).toEqual(TxStatus.MINED);
});
});
17 changes: 13 additions & 4 deletions yarn-project/scripts/src/benchmarks/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,17 @@ function processTxPXEProcessingStats(entry: TxPXEProcessingStats, results: Bench
}

/** Process entries for events tx-public-part-processed, grouped by public data writes */
function processTxSequencerProcessingStats(entry: TxSequencerProcessingStats, results: BenchmarkCollectedResults) {
function processTxSequencerProcessingStats(
entry: TxSequencerProcessingStats,
results: BenchmarkCollectedResults,
fileName: string,
) {
append(results, 'tx_sequencer_processing_time_ms', entry.publicDataUpdateRequests, entry.duration);
// only track specific txs to ensure they're doing the same thing
// TODO(alexg): need a better way to identify these txs
if (entry.classRegisteredCount === 0 && entry.newCommitmentCount >= 2 && fileName.includes('bench-tx-size')) {
append(results, 'tx_with_fee_size_in_bytes', entry.feePaymentMethod, entry.effectsSize);
}
}

/** Process a tree insertion event and updates results */
Expand Down Expand Up @@ -192,7 +201,7 @@ function processTreeInsertion(entry: TreeInsertionStats, results: BenchmarkColle
}

/** Processes a parsed entry from a log-file and updates results */
function processEntry(entry: Stats, results: BenchmarkCollectedResults) {
function processEntry(entry: Stats, results: BenchmarkCollectedResults, fileName: string) {
switch (entry.eventName) {
case 'rollup-published-to-l1':
return processRollupPublished(entry, results);
Expand All @@ -211,7 +220,7 @@ function processEntry(entry: Stats, results: BenchmarkCollectedResults) {
case 'tx-pxe-processing':
return processTxPXEProcessingStats(entry, results);
case 'tx-sequencer-processing':
return processTxSequencerProcessingStats(entry, results);
return processTxSequencerProcessingStats(entry, results, fileName);
case 'tree-insertion':
return processTreeInsertion(entry, results);
default:
Expand Down Expand Up @@ -240,7 +249,7 @@ export async function main() {

for await (const line of rl) {
const entry = JSON.parse(line);
processEntry(entry, collected);
processEntry(entry, collected, path.basename(filePath));
}
}

Expand Down
4 changes: 4 additions & 0 deletions yarn-project/scripts/src/benchmarks/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export function getMarkdown() {
const metricsByChainLength = Metrics.filter(m => m.groupBy === 'chain-length').map(m => m.name);
const metricsByCircuitName = Metrics.filter(m => m.groupBy === 'circuit-name').map(m => m.name);
const metricsByClassesRegistered = Metrics.filter(m => m.groupBy === 'classes-registered').map(m => m.name);
const metricsByFeePaymentMethod = Metrics.filter(m => m.groupBy === 'fee-payment-method').map(m => m.name);
const metricsByLeafCount = Metrics.filter(m => m.groupBy === 'leaf-count').map(m => m.name);

const metricsTxPxeProcessing = Metrics.filter(m => m.name === 'tx_pxe_processing_time_ms').map(m => m.name);
Expand Down Expand Up @@ -242,6 +243,9 @@ ${getTableContent(pick(benchmark, metricsByLeafCount), baseBenchmark, 'leaves')}
Transaction sizes based on how many contract classes are registered in the tx.
${getTableContent(pick(benchmark, metricsByClassesRegistered), baseBenchmark, 'registered classes')}
Transaction size based on fee payment method
${getTableContent(pick(benchmark, metricsByFeePaymentMethod), baseBenchmark, 'fee payment method')}
Transaction processing duration by data writes.
${getTableContent(pick(benchmark, metricsTxPxeProcessing), baseBenchmark, 'new note hashes')}
${getTableContent(pick(benchmark, metricsTxSeqProcessing), baseBenchmark, 'public data writes')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getPreviousOutputAndProof,
makeEmptyProcessedTx,
makeProcessedTx,
toTxEffect,
validateProcessedTx,
} from '@aztec/circuit-types';
import { TxSequencerProcessingStats } from '@aztec/circuit-types/stats';
Expand Down Expand Up @@ -133,6 +134,7 @@ export class PublicProcessor {
this.log(`Processed public part of ${tx.data.endNonRevertibleData.newNullifiers[0].value}`, {
eventName: 'tx-sequencer-processing',
duration: timer.ms(),
effectsSize: toTxEffect(processedTransaction).toBuffer().length,
publicDataUpdateRequests:
processedTransaction.data.combinedData.publicDataUpdateRequests.filter(x => !x.leafSlot.isZero()).length ??
0,
Expand Down

0 comments on commit 543f8a2

Please sign in to comment.