diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e10728f0b1..a7c3ad3901f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 @@ -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. diff --git a/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts index 04627578c64..21aba74b258 100644 --- a/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts @@ -53,11 +53,6 @@ export class NativeFeePaymentMethod implements FeePaymentMethod { */ getFunctionCalls(feeLimit: Fr): Promise { 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), diff --git a/yarn-project/circuit-types/src/stats/metrics.ts b/yarn-project/circuit-types/src/stats/metrics.ts index a27e8c6f0b4..084fc058435 100644 --- a/yarn-project/circuit-types/src/stats/metrics.ts +++ b/yarn-project/circuit-types/src/stats/metrics.ts @@ -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 { @@ -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', diff --git a/yarn-project/circuit-types/src/stats/stats.ts b/yarn-project/circuit-types/src/stats/stats.ts index b08b7b50ae1..14d159a1e99 100644 --- a/yarn-project/circuit-types/src/stats/stats.ts +++ b/yarn-project/circuit-types/src/stats/stats.ts @@ -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'; }; /** @@ -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; /** * Stats for tree insertions diff --git a/yarn-project/circuit-types/src/tx/tx.ts b/yarn-project/circuit-types/src/tx/tx.ts index ab9d06f87ca..4c9497fcd89 100644 --- a/yarn-project/circuit-types/src/tx/tx.ts +++ b/yarn-project/circuit-types/src/tx/tx.ts @@ -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)) diff --git a/yarn-project/end-to-end/scripts/docker-compose-no-sandbox.yml b/yarn-project/end-to-end/scripts/docker-compose-no-sandbox.yml index 41c5beb47a3..f9e1686ceff 100644 --- a/yarn-project/end-to-end/scripts/docker-compose-no-sandbox.yml +++ b/yarn-project/end-to-end/scripts/docker-compose-no-sandbox.yml @@ -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 diff --git a/yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts new file mode 100644 index 00000000000..6d10e25d0ce --- /dev/null +++ b/yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts @@ -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>([ + () => 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); + }); +}); diff --git a/yarn-project/scripts/src/benchmarks/aggregate.ts b/yarn-project/scripts/src/benchmarks/aggregate.ts index 54bdb56dd4f..528dda9b2ca 100644 --- a/yarn-project/scripts/src/benchmarks/aggregate.ts +++ b/yarn-project/scripts/src/benchmarks/aggregate.ts @@ -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 */ @@ -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); @@ -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: @@ -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)); } } diff --git a/yarn-project/scripts/src/benchmarks/markdown.ts b/yarn-project/scripts/src/benchmarks/markdown.ts index bdd88c5f0c6..2887805cfdc 100644 --- a/yarn-project/scripts/src/benchmarks/markdown.ts +++ b/yarn-project/scripts/src/benchmarks/markdown.ts @@ -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); @@ -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')} diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.ts index 98a1b4a4a71..50a6272df53 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.ts @@ -7,6 +7,7 @@ import { getPreviousOutputAndProof, makeEmptyProcessedTx, makeProcessedTx, + toTxEffect, validateProcessedTx, } from '@aztec/circuit-types'; import { TxSequencerProcessingStats } from '@aztec/circuit-types/stats'; @@ -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,