Skip to content

Commit

Permalink
feat: private da gas metering (#6103)
Browse files Browse the repository at this point in the history
# Context
At the end of the private kernel, we either use the `PrivateKernelTail`
or `PrivateKernelTailToPublic`:
if `PrivateKernelTail`, we are heading straight to the base rollup
because there is no public component and we have a
`CombinedAccumulatedData` struct as part of the public inputs, called
`end`, which has a `gas_used` (which has L2 and DA gas); if
`PrivateKernelTailToPublic`, there is a public component, so we split
the side effects from private execution into `end` and
`end_non_revertible`, each of which has a `gas_used`.

Without this change, we were only populating `end.gas_used` to be equal
to the `teardown_gas` which the used supplied for the transaction.

# With this change

## In `PrivateKernelTail`
We compute the amount of "metered gas" in the
PrivateAccumulatedDataBuilder. This returns the amount of DA and L2 gas
the transaction has consumed as part of private execution. L2 gas is
presently zero, but that will likely change (see the warning under the
L2 gas section
[here](https://docs.aztec.network/protocol-specs/gas-and-fees/fee-schedule)).

To this metered `Gas`, it adds the teardown gas and a "tx overhead"
`Gas`, which represents flat, fixed costs associated with every
transaction; it "tx overhead" presently just has a DA component.

## In `PrivateKernelTailToPublic`
We now sum "metered" gas associated with side effects while splitting
non/revertible side effects. The revertible gas used is initialized with
the teardown gas, and the "tx overhead" gas is added to non_revertible.

See
[spec](https://docs.aztec.network/protocol-specs/gas-and-fees/fee-schedule)
for more info.
  • Loading branch information
just-mitch authored Apr 30, 2024
1 parent 784d542 commit 1a8f372
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 46 deletions.
3 changes: 3 additions & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ library Constants {
uint256 internal constant DEFAULT_TEARDOWN_GAS_LIMIT = 100_000_000;
uint256 internal constant DEFAULT_MAX_FEE_PER_GAS = 10;
uint256 internal constant DEFAULT_INCLUSION_FEE = 0;
uint256 internal constant DA_BYTES_PER_FIELD = 32;
uint256 internal constant DA_GAS_PER_BYTE = 16;
uint256 internal constant FIXED_DA_GAS = 512;
uint256 internal constant CANONICAL_KEY_REGISTRY_ADDRESS =
0x1585e564a60e6ec974bc151b62705292ebfc75c33341986a47fd9749cedb567e;
uint256 internal constant AZTEC_ADDRESS_LENGTH = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use dep::types::{
abis::{
kernel_data::PrivateKernelData,
kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputsBuilder, PublicKernelCircuitPublicInputs},
note_hash::NoteHashContext, nullifier::Nullifier, side_effect::{SideEffect, Ordered}
note_hash::NoteHashContext, nullifier::Nullifier, side_effect::{SideEffect, Ordered}, gas::Gas
},
constants::{
MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX,
MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX,
MAX_ENCRYPTED_LOGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX
MAX_ENCRYPTED_LOGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, DA_BYTES_PER_FIELD, FIXED_DA_GAS,
DA_GAS_PER_BYTE
},
grumpkin_private_key::GrumpkinPrivateKey,
hash::{compute_note_hash_nonce, compute_unique_siloed_note_hash},
Expand Down Expand Up @@ -99,8 +100,6 @@ impl KernelCircuitPublicInputsComposer {

self.silo_values();

self.set_gas_used();

*self
}

Expand All @@ -113,18 +112,14 @@ impl KernelCircuitPublicInputsComposer {
}

pub fn finish(self) -> KernelCircuitPublicInputs {
self.public_inputs.finish_tail()
let teardown_gas = self.previous_kernel.public_inputs.constants.tx_context.gas_settings.teardown_gas_limits;
self.public_inputs.finish_tail(teardown_gas)
}

pub fn finish_to_public(self) -> PublicKernelCircuitPublicInputs {
let min_revertible_side_effect_counter = self.previous_kernel.public_inputs.min_revertible_side_effect_counter;
self.public_inputs.finish_to_public(min_revertible_side_effect_counter)
}

fn set_gas_used(&mut self) {
// TODO(gas): Compute DA gas used here and add to public_inputs.(end,end_non_revertible).gas_used
let teardown_gas = self.previous_kernel.public_inputs.constants.tx_context.gas_settings.teardown_gas_limits;
self.public_inputs.end.gas_used = teardown_gas;
self.public_inputs.finish_to_public(teardown_gas, min_revertible_side_effect_counter)
}

fn silo_values(&mut self) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ mod tests {
};
use dep::types::constants::{
MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX,
MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX
MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX,
DA_BYTES_PER_FIELD, DA_GAS_PER_BYTE
};
use dep::types::{
abis::{
Expand Down Expand Up @@ -508,6 +509,8 @@ mod tests {
let public_inputs = builder.execute();
assert_eq(array_length(public_inputs.end.new_note_hashes), 2);
assert_eq(array_length(public_inputs.end.new_nullifiers), 3);
let expected_gas = Gas::tx_overhead() + Gas::new(DA_GAS_PER_BYTE * DA_BYTES_PER_FIELD * 5, 0);
assert_eq(public_inputs.end.gas_used, expected_gas);
}

#[test(should_fail_with="Hinted note hash does not match")]
Expand Down Expand Up @@ -558,12 +561,15 @@ mod tests {
}

#[test]
unconstrained fn set_teardown_gas_as_gas_used() {
// TODO(gas): When we compute DA gas used, we'll have to include it here as well.
unconstrained fn empty_tx_consumes_teardown_limits_plus_fixed_gas() {
let mut builder = PrivateKernelTailInputsBuilder::new();
builder.previous_kernel.tx_context.gas_settings.teardown_gas_limits = Gas::new(300, 300);
let public_inputs = builder.execute();

assert_eq(public_inputs.end.gas_used, Gas::new(300, 300));

let expected_gas_consumed = Gas::new(300, 300) // teardown gas
+ Gas::tx_overhead() // tx overhead
+ Gas::new(DA_GAS_PER_BYTE * DA_BYTES_PER_FIELD * 1, 0); // tx nullifier
assert_eq(public_inputs.end.gas_used, expected_gas_consumed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ mod tests {
};
use dep::types::constants::{
MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX,
MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX
MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX,
DA_BYTES_PER_FIELD, DA_GAS_PER_BYTE
};
use dep::types::{
abis::{
Expand Down Expand Up @@ -532,6 +533,11 @@ mod tests {
[new_nullifiers[3], new_nullifiers[4]]
)
);

assert_eq(public_inputs.end.gas_used, Gas::new(2 * DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE, 0));
assert_eq(
public_inputs.end_non_revertible.gas_used, Gas::new(3 * DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE, 0) + Gas::tx_overhead()
);
}

#[test]
Expand Down Expand Up @@ -586,12 +592,12 @@ mod tests {
}

#[test]
unconstrained fn set_teardown_gas_as_gas_used() {
// TODO(gas): When we compute DA gas used, we'll have to include it here as well.
unconstrained fn empty_tx_consumes_teardown_limits_plus_fixed_gas() {
let mut builder = PrivateKernelTailToPublicInputsBuilder::new();
builder.previous_kernel.tx_context.gas_settings.teardown_gas_limits = Gas::new(300, 300);
let public_inputs = builder.execute();

assert_eq(public_inputs.end.gas_used, Gas::new(300, 300));
let expected_gas_consumed = Gas::new(300, 300) + Gas::tx_overhead();
assert_eq(public_inputs.end.gas_used, expected_gas_consumed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use crate::{
constants::{
MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX,
MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NEW_L2_TO_L1_MSGS_PER_TX,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_ENCRYPTED_LOGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_ENCRYPTED_LOGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX,
DA_BYTES_PER_FIELD, DA_GAS_PER_BYTE
},
traits::Empty
traits::{Empty, is_empty}
};

// Builds via PrivateKernelCircuitPublicInputsBuilder:
Expand Down Expand Up @@ -60,10 +61,11 @@ impl PrivateAccumulatedDataBuilder {
}
}

pub fn to_combined(self) -> CombinedAccumulatedData {
pub fn to_combined(self, teardown_gas: Gas) -> CombinedAccumulatedData {
// TODO(Miranda): Hash here or elsewhere?
let encrypted_logs_hash = compute_tx_logs_hash(self.encrypted_logs_hashes.storage);
let unencrypted_logs_hash = compute_tx_logs_hash(self.unencrypted_logs_hashes.storage);
let gas_used = self.to_metered_gas_used() + Gas::tx_overhead() + teardown_gas;

CombinedAccumulatedData {
new_note_hashes: self.new_note_hashes.storage.map(|n: NoteHashContext| n.value),
Expand All @@ -74,36 +76,88 @@ impl PrivateAccumulatedDataBuilder {
encrypted_log_preimages_length: self.encrypted_log_preimages_length,
unencrypted_log_preimages_length: self.unencrypted_log_preimages_length,
public_data_update_requests: [PublicDataUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX],
gas_used: self.gas_used.add(self.non_revertible_gas_used)
gas_used
}
}

pub fn to_metered_gas_used(self) -> Gas {
let mut metered_bytes = 0;

// note_hash_gas
for i in 0..self.new_note_hashes.storage.len() {
if !is_empty(self.new_note_hashes.get_unchecked(i)) {
metered_bytes += DA_BYTES_PER_FIELD;
}
}

// nullifier_gas
for i in 0..self.new_nullifiers.storage.len() {
if !is_empty(self.new_nullifiers.get_unchecked(i)) {
metered_bytes += DA_BYTES_PER_FIELD;
}
}

// l2_to_l1_msg_gas
for i in 0..self.new_l2_to_l1_msgs.storage.len() {
if !is_empty(self.new_l2_to_l1_msgs.get_unchecked(i)) {
metered_bytes += DA_BYTES_PER_FIELD;
}
}

// encrypted_logs_hash_gas
metered_bytes += self.encrypted_log_preimages_length as u32;

// unencrypted_logs_hash_gas
metered_bytes += self.unencrypted_log_preimages_length as u32;

Gas::new(DA_GAS_PER_BYTE * metered_bytes, 0)
}

pub fn split_to_public(
self,
min_revertible_side_effect_counter: u32
min_revertible_side_effect_counter: u32,
teardown_gas: Gas
) -> (PublicAccumulatedData, PublicAccumulatedData) {
let mut non_revertible_builder = PublicAccumulatedDataBuilder::empty();
let mut revertible_builder = PublicAccumulatedDataBuilder::empty();
let mut non_revertible_da_gas_used = 0;
let mut non_revertible_l2_gas_used = 0;
let mut revertible_da_gas_used = teardown_gas.da_gas; // pre-pay for teardown gas
let mut revertible_l2_gas_used = teardown_gas.l2_gas;
let DA_GAS_PER_FIELD = DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE;

for i in 0..MAX_NEW_NOTE_HASHES_PER_TX {
let note_hash = self.new_note_hashes.storage[i];
let public_note_hash = note_hash.expose_to_public();
if note_hash.counter < min_revertible_side_effect_counter {
non_revertible_builder.new_note_hashes.push(public_note_hash);
if !is_empty(public_note_hash) {
non_revertible_da_gas_used += DA_GAS_PER_FIELD ;
}
} else {
revertible_builder.new_note_hashes.push(public_note_hash);
if !is_empty(public_note_hash) {
revertible_da_gas_used += DA_GAS_PER_FIELD;
}
}
}

for i in 0..MAX_NEW_NULLIFIERS_PER_TX {
let nullifier = self.new_nullifiers.storage[i];
if nullifier.counter < min_revertible_side_effect_counter {
non_revertible_builder.new_nullifiers.push(nullifier);
if !is_empty(nullifier) {
non_revertible_da_gas_used += DA_GAS_PER_FIELD;
}
} else {
revertible_builder.new_nullifiers.push(nullifier);
if !is_empty(nullifier) {
revertible_da_gas_used += DA_GAS_PER_FIELD;
}
}
}

// TODO(gas): add AVM_STARTUP_L2_GAS here
for i in 0..MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX {
let call_stack_item = self.public_call_stack.storage[i];
if call_stack_item.start_side_effect_counter < min_revertible_side_effect_counter {
Expand Down Expand Up @@ -136,8 +190,10 @@ impl PrivateAccumulatedDataBuilder {
revertible_builder.encrypted_log_preimages_length = self.encrypted_log_preimages_length;
revertible_builder.unencrypted_log_preimages_length = self.unencrypted_log_preimages_length;

revertible_builder.gas_used = self.gas_used;
non_revertible_builder.gas_used = self.non_revertible_gas_used;
revertible_da_gas_used += DA_GAS_PER_BYTE * (self.encrypted_log_preimages_length as u32 + self.unencrypted_log_preimages_length as u32);

revertible_builder.gas_used = Gas::new(revertible_da_gas_used, revertible_l2_gas_used);
non_revertible_builder.gas_used = Gas::tx_overhead() + Gas::new(non_revertible_da_gas_used, non_revertible_l2_gas_used);
(non_revertible_builder.finish(), revertible_builder.finish())
}
}
Expand All @@ -150,7 +206,7 @@ mod tests {
note_hash::{NoteHash, NoteHashContext}, nullifier::Nullifier,
public_data_update_request::PublicDataUpdateRequest, side_effect::SideEffect
},
address::AztecAddress, utils::arrays::array_eq
address::AztecAddress, utils::arrays::array_eq, constants::{DA_BYTES_PER_FIELD, DA_GAS_PER_BYTE}
};

#[test]
Expand Down Expand Up @@ -213,10 +269,7 @@ mod tests {
builder.public_call_stack.extend_from_array(non_revertible_public_stack);
builder.public_call_stack.extend_from_array(revertible_public_call_stack);

builder.gas_used = Gas::new(20,20);
builder.non_revertible_gas_used = Gas::new(10,10);

let (non_revertible, revertible) = builder.split_to_public(7);
let (non_revertible, revertible) = builder.split_to_public(7, Gas::new(42, 17));

assert(
array_eq(
Expand All @@ -242,8 +295,14 @@ mod tests {
assert(array_eq(revertible.new_nullifiers, revertible_nullifiers));
assert(array_eq(revertible.public_call_stack, revertible_public_call_stack));

assert_eq(revertible.gas_used, Gas::new(20, 20));
assert_eq(non_revertible.gas_used, Gas::new(10, 10));
assert_eq(
revertible.gas_used, Gas::new(4 * DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE, 0) + Gas::new(42, 17)
);

print(non_revertible.gas_used);
assert_eq(
non_revertible.gas_used, Gas::new(4 * DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE, 0) + Gas::tx_overhead()
);
}
}

Expand Down
26 changes: 18 additions & 8 deletions noir-projects/noir-protocol-circuits/crates/types/src/abis/gas.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{
abis::function_selector::FunctionSelector, address::{EthAddress, AztecAddress},
constants::GAS_LENGTH, hash::pedersen_hash, traits::{Deserialize, Hash, Serialize, Empty},
abis::side_effect::Ordered, utils::reader::Reader, abis::gas_fees::GasFees
constants::{GAS_LENGTH, FIXED_DA_GAS}, hash::pedersen_hash,
traits::{Deserialize, Hash, Serialize, Empty}, abis::side_effect::Ordered, utils::reader::Reader,
abis::gas_fees::GasFees
};
use dep::std::ops::{Add, Sub};

struct Gas {
da_gas: u32,
Expand All @@ -14,12 +16,8 @@ impl Gas {
Self { da_gas, l2_gas }
}

pub fn add(self, other: Gas) -> Self {
Gas::new(self.da_gas + other.da_gas, self.l2_gas + other.l2_gas)
}

pub fn sub(self, other: Gas) -> Self {
Gas::new(self.da_gas - other.da_gas, self.l2_gas - other.l2_gas)
pub fn tx_overhead() -> Self {
Self { da_gas: FIXED_DA_GAS, l2_gas: 0 }
}

pub fn compute_fee(self, fees: GasFees) -> Field {
Expand All @@ -31,6 +29,18 @@ impl Gas {
}
}

impl Add for Gas {
fn add(self, other: Gas) -> Self {
Gas::new(self.da_gas + other.da_gas, self.l2_gas + other.l2_gas)
}
}

impl Sub for Gas {
fn sub(self, other: Gas) -> Self {
Gas::new(self.da_gas - other.da_gas, self.l2_gas - other.l2_gas)
}
}

impl Serialize<GAS_LENGTH> for Gas {
fn serialize(self) -> [Field; GAS_LENGTH] {
[self.da_gas as Field, self.l2_gas as Field]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
private_kernel_circuit_public_inputs::PrivateKernelCircuitPublicInputs,
public_kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs
},
validation_requests::validation_requests_builder::ValidationRequestsBuilder
gas::Gas, validation_requests::validation_requests_builder::ValidationRequestsBuilder
},
mocked::AggregationObject, partial_state_reference::PartialStateReference, traits::Empty
};
Expand Down Expand Up @@ -34,19 +34,23 @@ impl PrivateKernelCircuitPublicInputsBuilder {
}
}

pub fn finish_tail(self) -> KernelCircuitPublicInputs {
pub fn finish_tail(self, teardown_gas: Gas) -> KernelCircuitPublicInputs {
KernelCircuitPublicInputs {
aggregation_object: self.aggregation_object,
rollup_validation_requests: self.validation_requests.to_rollup(),
end: self.end.to_combined(),
end: self.end.to_combined(teardown_gas),
constants: self.constants,
start_state: PartialStateReference::empty(),
revert_code: 0
}
}

pub fn finish_to_public(self, min_revertible_side_effect_counter: u32) -> PublicKernelCircuitPublicInputs {
let (end_non_revertible, end) = self.end.split_to_public(min_revertible_side_effect_counter);
pub fn finish_to_public(
self,
teardown_gas: Gas,
min_revertible_side_effect_counter: u32
) -> PublicKernelCircuitPublicInputs {
let (end_non_revertible, end) = self.end.split_to_public(min_revertible_side_effect_counter, teardown_gas);

PublicKernelCircuitPublicInputs {
aggregation_object: self.aggregation_object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ global DEFAULT_GAS_LIMIT: u32 = 1_000_000_000;
global DEFAULT_TEARDOWN_GAS_LIMIT: u32 = 100_000_000;
global DEFAULT_MAX_FEE_PER_GAS: Field = 10;
global DEFAULT_INCLUSION_FEE: Field = 0;
global DA_BYTES_PER_FIELD: u32 = 32;
global DA_GAS_PER_BYTE: u32 = 16;
global FIXED_DA_GAS: u32 = 512;

global CANONICAL_KEY_REGISTRY_ADDRESS = 0x1585e564a60e6ec974bc151b62705292ebfc75c33341986a47fd9749cedb567e;

Expand Down
Loading

0 comments on commit 1a8f372

Please sign in to comment.