Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: benchmark tx size with fee #5414

Merged
merged 1 commit into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we make this assumption on mainnet? Just wondering.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a weak assumption. I think it's ok for benchmarking right now, but will have to be changed later. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Fine for now, but we probably want some clarity, first for ourselves, then for other sequencers on how to interpret what is going on in setup and teardown.

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's like we want to be able to set tracing on individual transactions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created #5445

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
Loading