Skip to content

Commit

Permalink
feat: include transaction fee in txreceipt (AztecProtocol#6139)
Browse files Browse the repository at this point in the history
This includes the `transaction_fee` as part of the `TxReceipt`, and
starts making assertions about actual fees paid.
  • Loading branch information
just-mitch authored May 3, 2024
1 parent 5ade36e commit 6785512
Show file tree
Hide file tree
Showing 15 changed files with 196 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export class BlockStore {
txHash,
tx.revertCode.isOK() ? TxStatus.MINED : TxStatus.REVERTED,
'',
tx.transactionFee.toBigInt(),
block.hash().toBuffer(),
block.number,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,18 @@ export class MemoryArchiverStore implements ArchiverDataStore {
*/
public getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
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,
),
);
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/circuit-types/src/tx/tx_receipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)}
}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)}]
}`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}`;
}
Expand Down
28 changes: 28 additions & 0 deletions yarn-project/circuits.js/src/structs/validation_requests.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -102,4 +104,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(', ')}]
}`;
}
}
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/jest.integration.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"moduleNameMapper": {
"^(\\.{1,2}/.*)\\.js$": "$1"
},
"reporters": [["default", {"summaryThreshold": 9999}]],
"reporters": [["default", { "summaryThreshold": 9999 }]],
"testRegex": "./src/.*\\.test\\.ts$",
"rootDir": "./src"
}
56 changes: 42 additions & 14 deletions yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,47 @@ describe('benchmarks/tx_size_fees', () => {
await token.methods.mint_public(aliceWallet.getAddress(), 100e9).send().wait();
});

it.each<[string, () => Promise<FeePaymentMethod | undefined>]>([
['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<FeePaymentMethod | undefined>, 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);
},
);
});
33 changes: 28 additions & 5 deletions yarn-project/end-to-end/src/e2e_fees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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],
Expand Down
11 changes: 10 additions & 1 deletion yarn-project/p2p/src/client/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/package.common.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"moduleNameMapper": {
"^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
},
"reporters": [["default", {"summaryThreshold": 9999}]],
"reporters": [["default", { "summaryThreshold": 9999 }]],
"testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
"rootDir": "./src"
}
Expand Down

0 comments on commit 6785512

Please sign in to comment.