Skip to content

Commit

Permalink
feat: benchmark tx size with fee
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr committed Mar 26, 2024
1 parent 22e0f0d commit 7ec193f
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 10 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 @@ -1541,10 +1551,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 received in the mempool.',
events: ['tx-added-to-pool'],
},
{
name: 'tx_pxe_processing_time_ms',
groupBy: 'data-writes',
Expand Down
3 changes: 3 additions & 0 deletions 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,6 +170,7 @@ 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;
effectsSize: number;
} & TxStats;

/**
Expand Down
9 changes: 9 additions & 0 deletions yarn-project/circuit-types/src/tx/processed_tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
} from '@aztec/circuits.js';
import { Tuple } from '@aztec/foundation/serialize';

import { TxStats } from '../stats/stats.js';

/**
* Represents a tx that has been processed by the sequencer public processor,
* so its kernel circuit public inputs are filled in.
Expand All @@ -38,6 +40,11 @@ export type ProcessedTx = Pick<Tx, 'proof' | 'encryptedLogs' | 'unencryptedLogs'
* Reason the tx was reverted.
*/
revertReason: SimulationError | undefined;

/**
* Used for debugging purposes, the fee payment method used for this tx.
*/
feePaymentMethod: TxStats['feePaymentMethod'];
};

export type RevertedTx = ProcessedTx & {
Expand Down Expand Up @@ -147,6 +154,7 @@ export function makeProcessedTx(
unencryptedLogs: revertReason ? new TxL2Logs([]) : tx.unencryptedLogs,
isEmpty: false,
revertReason,
feePaymentMethod: tx.getStats().feePaymentMethod,
};
}

Expand All @@ -170,6 +178,7 @@ export function makeEmptyProcessedTx(header: Header, chainId: Fr, version: Fr):
proof: emptyProof,
isEmpty: true,
revertReason: undefined,
feePaymentMethod: 'none',
};
}

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);
});
});
16 changes: 12 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,16 @@ 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 these stats from a specific file to ensure all txs are the same "shape"
if (entry.classRegisteredCount === 0 && 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 +200,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 +219,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 +248,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 @@ -132,6 +132,7 @@ describe('public_processor', () => {
unencryptedLogs: tx.unencryptedLogs,
isEmpty: false,
revertReason: undefined,
feePaymentMethod: tx.getStats().feePaymentMethod,
};

// Jest is complaining that the two objects are not equal, but they are.
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 7ec193f

Please sign in to comment.