From 43a3951471645bdc43f71fc0d671d4004021c0bb Mon Sep 17 00:00:00 2001 From: just-mitch Date: Wed, 1 May 2024 20:33:08 +0000 Subject: [PATCH] feat: include transaction fee in txreceipt --- .../crates/public-kernel-lib/src/common.nr | 6 +- .../src/public_kernel_teardown.nr | 11 ++-- .../combined_accumulated_data.nr | 2 +- .../archiver/kv_archiver_store/block_store.ts | 1 + .../memory_archiver_store.ts | 26 ++++++--- .../circuit-types/src/tx/tx_receipt.ts | 7 ++- .../kernel/combined_accumulated_data.ts | 28 ++++++++++ .../structs/kernel/public_accumulated_data.ts | 37 +++++++++--- .../public_kernel_circuit_public_inputs.ts | 2 +- .../src/structs/validation_requests.ts | 28 ++++++++++ .../src/benchmarks/bench_tx_size_fees.test.ts | 56 ++++++++++++++----- yarn-project/end-to-end/src/e2e_fees.test.ts | 33 +++++++++-- yarn-project/p2p/src/client/mocks.ts | 11 +++- 13 files changed, 199 insertions(+), 49 deletions(-) diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr index d25e42f490a..9cdbd551025 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr @@ -232,7 +232,7 @@ pub fn update_non_revertible_gas_used(public_call: PublicCallData, circuit_outpu let accum_end_gas_used = circuit_outputs.end.gas_used; // dep::types::debug_log::debug_log_format( - // "Updating non-revertible gas: limit.da={0} limit.l1={1} limit.l2={2} left.da={3} left.l1={4} left.l2={5} used.da={6} used.l1={7} used.l2={8}", + // "Updating non-revertible gas: limit.da={0} limit.l2={1} left.da={2} left.l2={3} used.da={4} used.l2={5}", // [ // tx_gas_limits.da_gas as Field, // tx_gas_limits.l2_gas as Field, @@ -243,10 +243,6 @@ pub fn update_non_revertible_gas_used(public_call: PublicCallData, circuit_outpu // ] // ); - // println( - // f"Updating non-revertible gas: tx_gas_limits={tx_gas_limits} call_gas_left={call_gas_left} accum_end_gas_used={accum_end_gas_used}" - // ); - circuit_outputs.end_non_revertible.gas_used = tx_gas_limits .sub(call_gas_left) .sub(accum_end_gas_used); diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_teardown.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_teardown.nr index 6c877b458e7..611fd16890b 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_teardown.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_teardown.nr @@ -35,16 +35,19 @@ impl PublicKernelTeardownCircuitPrivateInputs { fn validate_transaction_fee(self, public_inputs: PublicKernelCircuitPublicInputsBuilder) { let transaction_fee = self.public_call.call_stack_item.public_inputs.transaction_fee; // Note that teardown_gas is already included in end.gas_used as it was injected by the private kernel - let total_gas_used = self.previous_kernel.public_inputs.end.gas_used.add(self.previous_kernel.public_inputs.end_non_revertible.gas_used); + let total_gas_used = self.previous_kernel.public_inputs.end.gas_used + + self.previous_kernel.public_inputs.end_non_revertible.gas_used; let block_gas_fees = public_inputs.constants.global_variables.gas_fees; let inclusion_fee = self.previous_kernel.public_inputs.constants.tx_context.gas_settings.inclusion_fee; let computed_transaction_fee = total_gas_used.compute_fee(block_gas_fees) + inclusion_fee; // dep::types::debug_log::debug_log_format( - // "Validating tx fee: total_gas_used.da={0} total_gas_used.l1={1} total_gas_used.l2={2} block_fee_per_gas.da={3} block_fee_per_gas.l1={4} block_fee_per_gas.l2={5} inclusion_fee={6} computed={7} actual={8}", + // "Validating tx fee: end.gas_used.da={0} end.gas_used.l2={1} non_revertible.gas_used.da={2} non_revertible.gas_used.l2={3} block_fee_per_gas.da={4} block_fee_per_gas.l2={5} inclusion_fee={6} computed={7} actual={8}", // [ - // total_gas_used.da_gas as Field, - // total_gas_used.l2_gas as Field, + // self.previous_kernel.public_inputs.end.gas_used.da_gas as Field, + // self.previous_kernel.public_inputs.end.gas_used.l2_gas as Field, + // self.previous_kernel.public_inputs.end_non_revertible.gas_used.da_gas as Field, + // self.previous_kernel.public_inputs.end_non_revertible.gas_used.l2_gas as Field, // block_gas_fees.fee_per_da_gas as Field, // block_gas_fees.fee_per_l2_gas as Field, // inclusion_fee, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr index ef21c2a34a6..e6907de06f4 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr @@ -59,7 +59,7 @@ impl CombinedAccumulatedData { non_revertible.public_data_update_requests, revertible.public_data_update_requests ), - gas_used: revertible.gas_used.add(non_revertible.gas_used) + gas_used: revertible.gas_used + non_revertible.gas_used } } } diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts index 716c71e1cf4..fd8460b65e3 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts @@ -140,6 +140,7 @@ export class BlockStore { txHash, tx.revertCode.isOK() ? TxStatus.MINED : TxStatus.REVERTED, '', + tx.transactionFee.toBigInt(), block.hash().toBuffer(), block.number, ); diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts index ccc3698fe7f..d956a3d968d 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts @@ -1,19 +1,19 @@ import { + ExtendedUnencryptedL2Log, + LogId, + LogType, + TxReceipt, + TxStatus, type Body, type EncryptedL2BlockL2Logs, - ExtendedUnencryptedL2Log, type FromLogType, type GetUnencryptedLogsResponse, type InboxLeaf, type L2Block, type L2BlockL2Logs, type LogFilter, - LogId, - LogType, type TxEffect, type TxHash, - TxReceipt, - TxStatus, type UnencryptedL2BlockL2Logs, } from '@aztec/circuit-types'; import { Fr, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js'; @@ -266,10 +266,18 @@ export class MemoryArchiverStore implements ArchiverDataStore { */ public getSettledTxReceipt(txHash: TxHash): Promise { for (const block of this.l2Blocks) { - const txHashes = block.body.txEffects.map(txEffect => txEffect.txHash); - for (const currentTxHash of txHashes) { - if (currentTxHash.equals(txHash)) { - return Promise.resolve(new TxReceipt(txHash, TxStatus.MINED, '', block.hash().toBuffer(), block.number)); + for (const txEffect of block.body.txEffects) { + if (txEffect.txHash.equals(txHash)) { + return Promise.resolve( + new TxReceipt( + txHash, + TxStatus.MINED, + '', + txEffect.transactionFee.toBigInt(), + block.hash().toBuffer(), + block.number, + ), + ); } } } diff --git a/yarn-project/circuit-types/src/tx/tx_receipt.ts b/yarn-project/circuit-types/src/tx/tx_receipt.ts index 1cf7ef0797c..bf4f85e277d 100644 --- a/yarn-project/circuit-types/src/tx/tx_receipt.ts +++ b/yarn-project/circuit-types/src/tx/tx_receipt.ts @@ -32,6 +32,10 @@ export class TxReceipt { * Description of transaction error, if any. */ public error: string, + /** + * The transaction fee paid for the transaction. + */ + public transactionFee?: bigint, /** * The hash of the block containing the transaction. */ @@ -69,9 +73,10 @@ export class TxReceipt { const txHash = TxHash.fromString(obj.txHash); const status = obj.status as TxStatus; const error = obj.error; + const transactionFee = obj.transactionFee; const blockHash = obj.blockHash ? Buffer.from(obj.blockHash, 'hex') : undefined; const blockNumber = obj.blockNumber ? Number(obj.blockNumber) : undefined; - return new TxReceipt(txHash, status, error, blockHash, blockNumber); + return new TxReceipt(txHash, status, error, transactionFee, blockHash, blockNumber); } } diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts index 3c4d58cf3ed..ba1343f86a2 100644 --- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts @@ -2,6 +2,8 @@ import { makeTuple } from '@aztec/foundation/array'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { inspect } from 'util'; + import { type MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_TX, @@ -116,4 +118,30 @@ export class CombinedAccumulatedData { Gas.empty(), ); } + + [inspect.custom]() { + return `CombinedAccumulatedData { + newNoteHashes: [${this.newNoteHashes + .filter(x => !x.isZero()) + .map(x => inspect(x)) + .join(', ')}], + newNullifiers: [${this.newNullifiers + .filter(x => !x.isZero()) + .map(x => inspect(x)) + .join(', ')}], + newL2ToL1Msgs: [${this.newL2ToL1Msgs + .filter(x => !x.isZero()) + .map(x => inspect(x)) + .join(', ')}], + encryptedLogsHash: ${this.encryptedLogsHash.toString()}, + unencryptedLogsHash: ${this.unencryptedLogsHash.toString()}, + encryptedLogPreimagesLength: ${this.encryptedLogPreimagesLength.toString()}, + unencryptedLogPreimagesLength: ${this.unencryptedLogPreimagesLength.toString()}, + publicDataUpdateRequests: [${this.publicDataUpdateRequests + .filter(x => !x.isEmpty()) + .map(x => inspect(x)) + .join(', ')}], + gasUsed: ${inspect(this.gasUsed)} + }`; + } } diff --git a/yarn-project/circuits.js/src/structs/kernel/public_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/public_accumulated_data.ts index 3b5ff5917f5..792891fc9c7 100644 --- a/yarn-project/circuits.js/src/structs/kernel/public_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/public_accumulated_data.ts @@ -103,16 +103,37 @@ export class PublicAccumulatedData { [inspect.custom]() { // print out the non-empty fields return `PublicAccumulatedData { - newNoteHashes: [${this.newNoteHashes.map(h => h.toString()).join(', ')}], - newNullifiers: [${this.newNullifiers.map(h => h.toString()).join(', ')}], - newL2ToL1Msgs: [${this.newL2ToL1Msgs.map(h => h.toString()).join(', ')}], - encryptedLogsHashes: [${this.encryptedLogsHashes.map(h => h.toString()).join(', ')}], - unencryptedLogsHashes: [${this.unencryptedLogsHashes.map(h => h.toString()).join(', ')}], + newNoteHashes: [${this.newNoteHashes + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + newNullifiers: [${this.newNullifiers + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + newL2ToL1Msgs: [${this.newL2ToL1Msgs + .filter(x => !x.isZero()) + .map(h => inspect(h)) + .join(', ')}], + encryptedLogsHashes: [${this.encryptedLogsHashes + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + unencryptedLogsHashes: [${this.unencryptedLogsHashes + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], encryptedLogPreimagesLength: ${this.encryptedLogPreimagesLength} unencryptedLogPreimagesLength: ${this.unencryptedLogPreimagesLength} - publicDataUpdateRequests: [${this.publicDataUpdateRequests.map(h => h.toString()).join(', ')}], - publicCallStack: [${this.publicCallStack.map(h => h.toString()).join(', ')}], - gasUsed: [${this.gasUsed}] + publicDataUpdateRequests: [${this.publicDataUpdateRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + publicCallStack: [${this.publicCallStack + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + gasUsed: [${inspect(this.gasUsed)}] }`; } diff --git a/yarn-project/circuits.js/src/structs/kernel/public_kernel_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/public_kernel_circuit_public_inputs.ts index 87e2e9545e9..a12fd0d30b8 100644 --- a/yarn-project/circuits.js/src/structs/kernel/public_kernel_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/public_kernel_circuit_public_inputs.ts @@ -105,7 +105,7 @@ export class PublicKernelCircuitPublicInputs { validationRequests: ${inspect(this.validationRequests)}, endNonRevertibleData: ${inspect(this.endNonRevertibleData)}, end: ${inspect(this.end)}, - constants: ${this.constants}, + constants: ${inspect(this.constants)}, revertCode: ${this.revertCode} }`; } diff --git a/yarn-project/circuits.js/src/structs/validation_requests.ts b/yarn-project/circuits.js/src/structs/validation_requests.ts index faff930709e..1dfca548a48 100644 --- a/yarn-project/circuits.js/src/structs/validation_requests.ts +++ b/yarn-project/circuits.js/src/structs/validation_requests.ts @@ -1,6 +1,8 @@ import { makeTuple } from '@aztec/foundation/array'; import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { inspect } from 'util'; + import { MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, @@ -103,4 +105,30 @@ export class ValidationRequests { makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, PublicDataRead.empty), ); } + + [inspect.custom]() { + return `ValidationRequests { + forRollup: ${inspect(this.forRollup)}, + noteHashReadRequests: [${this.noteHashReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + nullifierReadRequests: [${this.nullifierReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + nullifierNonExistentReadRequests: [${this.nullifierNonExistentReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + nullifierKeyValidationRequests: [${this.nullifierKeyValidationRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}], + publicDataReads: [${this.publicDataReads + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}] +}`; + } } 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 index 19a2ceefbd6..26e03ead1e4 100644 --- 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 @@ -61,19 +61,47 @@ describe('benchmarks/tx_size_fees', () => { await token.methods.mint_public(aliceWallet.getAddress(), 100e9).send().wait(); }); - it.each<[string, () => Promise]>([ - ['no', () => Promise.resolve(undefined)], - ['native fee', () => NativeFeePaymentMethod.create(aliceWallet)], - ['public fee', () => Promise.resolve(new PublicFeePaymentMethod(token.address, fpc.address, aliceWallet))], - ['private fee', () => Promise.resolve(new PrivateFeePaymentMethod(token.address, fpc.address, aliceWallet))], - ] as const)('sends a tx with a fee with %s payment method', async (_name, createPaymentMethod) => { - const paymentMethod = await createPaymentMethod(); - const gasSettings = GasSettings.default(); - const tx = await token.methods - .transfer(aliceWallet.getAddress(), bobAddress, 1n, 0) - .send({ fee: paymentMethod ? { gasSettings, paymentMethod } : undefined }) - .wait(); + it.each<[string, () => Promise, bigint]>([ + ['no', () => Promise.resolve(undefined), 0n], + [ + 'native fee', + () => NativeFeePaymentMethod.create(aliceWallet), + // DA: + // non-rev: 1 nullifiers, overhead; rev: 2 note hashes, 1 nullifier, 624 B enc logs, 8 B unenc logs, teardown + // L2: + // non-rev: 0; rev: 0 + 200012672n, + ], + [ + 'public fee', + () => Promise.resolve(new PublicFeePaymentMethod(token.address, fpc.address, aliceWallet)), + // DA: + // non-rev: 1 nullifiers, overhead; rev: 2 note hashes, 1 nullifier, 628 B enc logs, 12 B unenc logs, teardown + // L2: + // non-rev: 0; rev: 0 + 200012800n, + ], + [ + 'private fee', + () => Promise.resolve(new PrivateFeePaymentMethod(token.address, fpc.address, aliceWallet)), + // DA: + // non-rev: 3 nullifiers, overhead; rev: 2 note hashes, 944 B enc logs, 20 B unenc logs, teardown + // L2: + // non-rev: 0; rev: 0 + 200018496n, + ], + ] as const)( + 'sends a tx with a fee with %s payment method', + async (_name, createPaymentMethod, expectedTransactionFee) => { + const paymentMethod = await createPaymentMethod(); + const gasSettings = GasSettings.default(); + const tx = await token.methods + .transfer(aliceWallet.getAddress(), bobAddress, 1n, 0) + .send({ fee: paymentMethod ? { gasSettings, paymentMethod } : undefined }) + .wait(); - expect(tx.status).toEqual(TxStatus.MINED); - }); + expect(tx.status).toEqual(TxStatus.MINED); + expect(tx.transactionFee).toEqual(expectedTransactionFee); + }, + ); }); diff --git a/yarn-project/end-to-end/src/e2e_fees.test.ts b/yarn-project/end-to-end/src/e2e_fees.test.ts index 8a25e7d77d4..b521e7c8ede 100644 --- a/yarn-project/end-to-end/src/e2e_fees.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees.test.ts @@ -238,15 +238,17 @@ describe('e2e_fees', () => { it('pays fees for tx that dont run public app logic', async () => { /** - * PRIVATE SETUP - * check authwit - * reduce alice BC.private by MaxFee + * PRIVATE SETUP (1 nullifier for tx) + * check authwit (1 nullifier) + * reduce alice BC.private by MaxFee (1 nullifier) * enqueue public call to increase FPC BC.public by MaxFee * enqueue public call for fpc.pay_fee_with_shielded_rebate * * PRIVATE APP LOGIC - * reduce Alice's BC.private by transferAmount - * create note for Bob of transferAmount + * reduce Alice's BC.private by transferAmount (1 note) + * create note for Bob of transferAmount (1 note) + * encrypted logs of 944 bytes + * unencrypted logs of 20 bytes * * PUBLIC SETUP * increase FPC BC.public by MaxFee @@ -280,6 +282,27 @@ describe('e2e_fees', () => { }) .wait(); + /** + * at present the user is paying DA gas for: + * 3 nullifiers = 3 * DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE = 3 * 32 * 16 = 1536 DA gas + * 2 note hashes = 2 * DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE = 2 * 32 * 16 = 1024 DA gas + * 964 bytes of logs = 964 * DA_GAS_PER_BYTE = 964 * 16 = 15424 DA gas + * tx overhead of 512 DA gas + * for a total of 18496 DA gas. + * + * The default teardown gas allocation at present is + * 100_000_000 for both DA and L2 gas. + * + * That produces a grand total of 200018496n. + * + * This will change because: + * 1. Gas use during public execution is not currently incorporated + * 2. We are presently squashing notes/nullifiers across non/revertible during private exeuction, + * but we shouldn't. + */ + + expect(tx.transactionFee).toEqual(200018496n); + await expectMapping( bananaPrivateBalances, [aliceAddress, bobAddress, bananaFPC.address, sequencerAddress], diff --git a/yarn-project/p2p/src/client/mocks.ts b/yarn-project/p2p/src/client/mocks.ts index 533017b587c..8fb30f41db4 100644 --- a/yarn-project/p2p/src/client/mocks.ts +++ b/yarn-project/p2p/src/client/mocks.ts @@ -78,7 +78,16 @@ export class MockBlockSource implements L2BlockSource { for (const block of this.l2Blocks) { for (const txEffect of block.body.txEffects) { if (txEffect.txHash.equals(txHash)) { - return Promise.resolve(new TxReceipt(txHash, TxStatus.MINED, '', block.hash().toBuffer(), block.number)); + return Promise.resolve( + new TxReceipt( + txHash, + TxStatus.MINED, + '', + txEffect.transactionFee.toBigInt(), + block.hash().toBuffer(), + block.number, + ), + ); } } }