Skip to content

Commit

Permalink
chore!: l2 gas maximum is per-TX-public-portion. AVM startup gas is n…
Browse files Browse the repository at this point in the history
…ow 20k. (#10214)

Resolves #10030

Bump of AVM startup gas to 20k is mostly arbitrary, but considering some
individual opcodes cost more than its previous value of 512, it
certainly needed to be much higher. I thought 20k is at least _more_
reasonable to account for the constraint cost of verifying an AVM proof.

The l2 gas maximum per-tx-public-portion ensures that there is some hard
limit on execution per AVM proof. For now, we use that limit to ensure
that you cannot overflow the AVM trace.

---------

Co-authored-by: IlyasRidhuan <[email protected]>
  • Loading branch information
2 people authored and lucasxia01 committed Dec 11, 2024
1 parent 4de8362 commit db37318
Show file tree
Hide file tree
Showing 14 changed files with 72 additions and 91 deletions.
21 changes: 0 additions & 21 deletions barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1739,27 +1739,6 @@ TEST_F(AvmExecutionTests, daGasLeft)
validate_trace(std::move(trace), public_inputs);
}

TEST_F(AvmExecutionTests, ExecutorThrowsWithTooMuchGasAllocated)
{
GTEST_SKIP();
std::string bytecode_hex = to_hex(OpCode::GETENVVAR_16) + // opcode GETENVVAR_16(sender)
"00" // Indirect flag
+ "0007" + to_hex(static_cast<uint8_t>(EnvironmentVariable::SENDER)); // addr 7

std::vector<FF> calldata = {};
std::vector<FF> returndata = {};
public_inputs.gas_settings.gas_limits.l2_gas = MAX_L2_GAS_PER_ENQUEUED_CALL;

auto bytecode = hex_to_bytes(bytecode_hex);
auto [instructions, error] = Deserialization::parse_bytecode_statically(bytecode);
ASSERT_TRUE(is_ok(error));

ExecutionHints execution_hints;
EXPECT_THROW_WITH_MESSAGE(gen_trace(bytecode, calldata, public_inputs, returndata, execution_hints),
"Cannot allocate more than MAX_L2_GAS_PER_ENQUEUED_CALL to the AVM for "
"execution of an enqueued call");
}

// Should throw whenever the wrong number of public inputs are provided
// TEST_F(AvmExecutionTests, ExecutorThrowsWithIncorrectNumberOfPublicInputs)
// {
Expand Down
15 changes: 0 additions & 15 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,6 @@ template <typename FF_> VmPublicInputs_<FF_> convert_public_inputs(std::vector<F
throw_or_abort("Public inputs vector is not of PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH");
}

// WARNING: this must be constrained by the kernel!
// Here this is just a sanity check to prevent generation of proofs that
// will be thrown out by the kernel anyway.
if constexpr (IsAnyOf<FF_, bb::fr>) {
if (public_inputs_vec[L2_START_GAS_LEFT_PCPI_OFFSET] > MAX_L2_GAS_PER_ENQUEUED_CALL) {
throw_or_abort(
"Cannot allocate more than MAX_L2_GAS_PER_ENQUEUED_CALL to the AVM for execution of an enqueued call");
}
} else {
if (public_inputs_vec[L2_START_GAS_LEFT_PCPI_OFFSET].get_value() > MAX_L2_GAS_PER_ENQUEUED_CALL) {
throw_or_abort(
"Cannot allocate more than MAX_L2_GAS_PER_ENQUEUED_CALL to the AVM for execution of an enqueued call");
}
}

std::array<FF_, KERNEL_INPUTS_LENGTH>& kernel_inputs = std::get<KERNEL_INPUTS>(public_inputs);

// Copy items from PublicCircuitPublicInputs vector to public input columns
Expand Down
9 changes: 8 additions & 1 deletion barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,16 @@ AvmTraceBuilder::AvmTraceBuilder(AvmPublicInputs public_inputs,
, bytecode_trace_builder(execution_hints.all_contract_bytecode)
, merkle_tree_trace_builder(public_inputs.start_tree_snapshots)
{
// Only allocate up to the maximum L2 gas for execution
// TODO: constrain this!
auto const l2_gas_left_after_private =
public_inputs.gas_settings.gas_limits.l2_gas - public_inputs.start_gas_used.l2_gas;
// TODO: think about cast
auto const allocated_l2_gas =
std::min(l2_gas_left_after_private, static_cast<uint32_t>(MAX_L2_GAS_PER_TX_PUBLIC_PORTION));
// TODO: think about cast
gas_trace_builder.set_initial_gas(
static_cast<uint32_t>(public_inputs.gas_settings.gas_limits.l2_gas - public_inputs.start_gas_used.l2_gas),
static_cast<uint32_t>(allocated_l2_gas),
static_cast<uint32_t>(public_inputs.gas_settings.gas_limits.da_gas - public_inputs.start_gas_used.da_gas));
}

Expand Down
2 changes: 1 addition & 1 deletion barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#define MAX_L2_TO_L1_MSGS_PER_TX 8
#define MAX_UNENCRYPTED_LOGS_PER_TX 8
#define MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS 3000
#define MAX_L2_GAS_PER_ENQUEUED_CALL 12000000
#define MAX_L2_GAS_PER_TX_PUBLIC_PORTION 12000000
#define CANONICAL_AUTH_REGISTRY_ADDRESS 1
#define DEPLOYER_CONTRACT_ADDRESS 2
#define REGISTERER_CONTRACT_ADDRESS 3
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ Further changes are planned, so that:

## 0.66

### L2 Gas limit of 12M enforced for public portion of TX

This limit was previously enforced per-enqueued-public-call. The protocol now enforces a stricter limit that the entire public portion of a transaction consumes at most 12,000,000 L2 gas.

### DEBUG env var is removed

The `DEBUG` variable is no longer used. Use `LOG_LEVEL` with one of `silent`, `fatal`, `error`, `warn`, `info`, `verbose`, `debug`, or `trace`. To tweak log levels per module, add a list of module prefixes with their overridden level. For example, LOG_LEVEL="info; verbose: aztec:sequencer, aztec:archiver; debug: aztec:kv-store" sets `info` as the default log level, `verbose` for the sequencer and archiver, and `debug` for the kv-store. Module name match is done by prefix.
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ library Constants {
14061769416655647708490531650437236735160113654556896985372298487345;
uint256 internal constant DEFAULT_GAS_LIMIT = 1000000000;
uint256 internal constant DEFAULT_TEARDOWN_GAS_LIMIT = 12000000;
uint256 internal constant MAX_L2_GAS_PER_ENQUEUED_CALL = 12000000;
uint256 internal constant MAX_L2_GAS_PER_TX_PUBLIC_PORTION = 12000000;
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 FIXED_L2_GAS = 512;
uint256 internal constant FIXED_AVM_STARTUP_L2_GAS = 1024;
uint256 internal constant FIXED_AVM_STARTUP_L2_GAS = 20000;
uint256 internal constant L2_GAS_DISTRIBUTED_STORAGE_PREMIUM = 1024;
uint256 internal constant L2_GAS_PER_READ_MERKLE_HASH = 30;
uint256 internal constant L2_GAS_PER_WRITE_MERKLE_HASH = 40;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,15 @@ pub global DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE: Field =
// GAS DEFAULTS
pub global DEFAULT_GAS_LIMIT: u32 = 1_000_000_000;
pub global DEFAULT_TEARDOWN_GAS_LIMIT: u32 = 12_000_000;
pub global MAX_L2_GAS_PER_ENQUEUED_CALL: u32 = 12_000_000;
pub global MAX_L2_GAS_PER_TX_PUBLIC_PORTION: u32 = 12_000_000;
pub global DA_BYTES_PER_FIELD: u32 = 32;
pub global DA_GAS_PER_BYTE: u32 = 16;
// pays for preamble information in TX Effects
pub global FIXED_DA_GAS: u32 = 512;
// pays for fixed tx costs like validation, and updating state roots
pub global FIXED_L2_GAS: u32 = 512;
// base cost for a single public call
pub global FIXED_AVM_STARTUP_L2_GAS: u32 = 1024;
pub global FIXED_AVM_STARTUP_L2_GAS: u32 = 20_000;

// Some tree insertions incur an additional cost associated with
// the new database entry to be stored by all network participants.
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ export const DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE =
14061769416655647708490531650437236735160113654556896985372298487345n;
export const DEFAULT_GAS_LIMIT = 1000000000;
export const DEFAULT_TEARDOWN_GAS_LIMIT = 12000000;
export const MAX_L2_GAS_PER_ENQUEUED_CALL = 12000000;
export const MAX_L2_GAS_PER_TX_PUBLIC_PORTION = 12000000;
export const DA_BYTES_PER_FIELD = 32;
export const DA_GAS_PER_BYTE = 16;
export const FIXED_DA_GAS = 512;
export const FIXED_L2_GAS = 512;
export const FIXED_AVM_STARTUP_L2_GAS = 1024;
export const FIXED_AVM_STARTUP_L2_GAS = 20000;
export const L2_GAS_DISTRIBUTED_STORAGE_PREMIUM = 1024;
export const L2_GAS_PER_READ_MERKLE_HASH = 30;
export const L2_GAS_PER_WRITE_MERKLE_HASH = 40;
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/circuits.js/src/scripts/constants.in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const CPP_CONSTANTS = [
'MEM_TAG_U64',
'MEM_TAG_U128',
'MEM_TAG_FF',
'MAX_L2_GAS_PER_ENQUEUED_CALL',
'MAX_L2_GAS_PER_TX_PUBLIC_PORTION',
'MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS',
'CANONICAL_AUTH_REGISTRY_ADDRESS',
'DEPLOYER_CONTRACT_ADDRESS',
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/simulator/src/avm/avm_simulator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type AztecAddress, Fr, type GlobalVariables, MAX_L2_GAS_PER_ENQUEUED_CALL } from '@aztec/circuits.js';
import { type AztecAddress, Fr, type GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js';
import { type Logger, createLogger } from '@aztec/foundation/log';

import { strict as assert } from 'assert';
Expand Down Expand Up @@ -47,8 +47,8 @@ export class AvmSimulator {
// only. Otherwise, use build() below.
constructor(private context: AvmContext, private instructionSet: InstructionSet = INSTRUCTION_SET()) {
assert(
context.machineState.gasLeft.l2Gas <= MAX_L2_GAS_PER_ENQUEUED_CALL,
`Cannot allocate more than ${MAX_L2_GAS_PER_ENQUEUED_CALL} to the AVM for execution of an enqueued call`,
context.machineState.gasLeft.l2Gas <= MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
`Cannot allocate more than ${MAX_L2_GAS_PER_TX_PUBLIC_PORTION} to the AVM for execution.`,
);
this.log = createLogger(`simulator:avm(calldata[0]: ${context.environment.calldata[0]})`);
// TODO(palla/log): Should tallies be printed on debug, or only on trace?
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isNoirCallStackUnresolved } from '@aztec/circuit-types';
import { GasFees, GlobalVariables, MAX_L2_GAS_PER_ENQUEUED_CALL } from '@aztec/circuits.js';
import { GasFees, GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js';
import { type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { EthAddress } from '@aztec/foundation/eth-address';
Expand Down Expand Up @@ -93,7 +93,7 @@ export function initGlobalVariables(overrides?: Partial<GlobalVariables>): Globa
*/
export function initMachineState(overrides?: Partial<AvmMachineState>): AvmMachineState {
return AvmMachineState.fromState({
l2GasLeft: overrides?.l2GasLeft ?? MAX_L2_GAS_PER_ENQUEUED_CALL,
l2GasLeft: overrides?.l2GasLeft ?? MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
daGasLeft: overrides?.daGasLeft ?? 1e8,
});
}
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/simulator/src/public/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
GasFees,
GasSettings,
GlobalVariables,
MAX_L2_GAS_PER_ENQUEUED_CALL,
MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
PartialPrivateTailPublicInputsForPublic,
PrivateKernelTailCircuitPublicInputs,
type PublicFunction,
Expand Down Expand Up @@ -113,7 +113,7 @@ export function createTxForPublicCall(
): Tx {
const callRequest = executionRequest.toCallRequest();
// use max limits
const gasLimits = new Gas(DEFAULT_GAS_LIMIT, MAX_L2_GAS_PER_ENQUEUED_CALL);
const gasLimits = new Gas(DEFAULT_GAS_LIMIT, MAX_L2_GAS_PER_TX_PUBLIC_PORTION);

const forPublic = PartialPrivateTailPublicInputsForPublic.empty();
// TODO(#9269): Remove this fake nullifier method as we move away from 1st nullifier as hash.
Expand Down
63 changes: 41 additions & 22 deletions yarn-project/simulator/src/public/public_tx_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
Gas,
type GasSettings,
type GlobalVariables,
MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
type PrivateToPublicAccumulatedData,
type PublicCallRequest,
PublicCircuitPublicInputs,
Expand All @@ -43,7 +44,7 @@ export class PublicTxContext {
private log: Logger;

/* Gas used including private, teardown gas _limit_, setup and app logic */
private gasUsed: Gas;
private gasUsedByPublic: Gas = Gas.empty();
/* Gas actually used during teardown (different from limit) */
public teardownGasUsed: Gas = Gas.empty();

Expand All @@ -60,8 +61,9 @@ export class PublicTxContext {
public readonly state: PhaseStateManager,
private readonly globalVariables: GlobalVariables,
private readonly startStateReference: StateReference,
private readonly startGasUsed: Gas,
private readonly gasSettings: GasSettings,
private readonly gasUsedByPrivate: Gas,
private readonly gasAllocatedToPublic: Gas,
private readonly setupCallRequests: PublicCallRequest[],
private readonly appLogicCallRequests: PublicCallRequest[],
private readonly teardownCallRequests: PublicCallRequest[],
Expand All @@ -73,7 +75,6 @@ export class PublicTxContext {
public trace: PublicEnqueuedCallSideEffectTrace, // FIXME(dbanks12): should be private
) {
this.log = createLogger(`simulator:public_tx_context`);
this.gasUsed = startGasUsed;
}

public static async create(
Expand All @@ -100,12 +101,18 @@ export class PublicTxContext {
// Transaction level state manager that will be forked for revertible phases.
const txStateManager = await AvmPersistableStateManager.create(worldStateDB, enqueuedCallTrace, doMerkleOperations);

const gasSettings = tx.data.constants.txContext.gasSettings;
const gasUsedByPrivate = tx.data.gasUsed;
// Gas allocated to public is "whatever's left" after private, but with some max applied.
const gasAllocatedToPublic = applyMaxToAvailableGas(gasSettings.gasLimits.sub(gasUsedByPrivate));

return new PublicTxContext(
new PhaseStateManager(txStateManager),
globalVariables,
await db.getStateReference(),
tx.data.gasUsed,
tx.data.constants.txContext.gasSettings,
gasSettings,
gasUsedByPrivate,
gasAllocatedToPublic,
getCallRequestsByPhase(tx, TxExecutionPhase.SETUP),
getCallRequestsByPhase(tx, TxExecutionPhase.APP_LOGIC),
getCallRequestsByPhase(tx, TxExecutionPhase.TEARDOWN),
Expand Down Expand Up @@ -226,13 +233,14 @@ export class PublicTxContext {
}

/**
* How much gas is left for the specified phase?
* How much gas is left as of the specified phase?
*/
getGasLeftForPhase(phase: TxExecutionPhase): Gas {
getGasLeftAtPhase(phase: TxExecutionPhase): Gas {
if (phase === TxExecutionPhase.TEARDOWN) {
return this.gasSettings.teardownGasLimits;
return applyMaxToAvailableGas(this.gasSettings.teardownGasLimits);
} else {
return this.gasSettings.gasLimits.sub(this.gasUsed);
const gasLeftForPublic = this.gasAllocatedToPublic.sub(this.gasUsedByPublic);
return gasLeftForPublic;
}
}

Expand All @@ -243,10 +251,18 @@ export class PublicTxContext {
if (phase === TxExecutionPhase.TEARDOWN) {
this.teardownGasUsed = this.teardownGasUsed.add(gas);
} else {
this.gasUsed = this.gasUsed.add(gas);
this.gasUsedByPublic = this.gasUsedByPublic.add(gas);
}
}

/**
* The gasUsed by public and private,
* as if the entire teardown gas limit was consumed.
*/
getTotalGasUsed(): Gas {
return this.gasUsedByPrivate.add(this.gasUsedByPublic);
}

/**
* Compute the gas used using the actual gas used during teardown instead
* of the teardown gas limit.
Expand All @@ -257,14 +273,7 @@ export class PublicTxContext {
assert(this.halted, 'Can only compute actual gas used after tx execution ends');
const requireTeardown = this.teardownCallRequests.length > 0;
const teardownGasLimits = requireTeardown ? this.gasSettings.teardownGasLimits : Gas.empty();
return this.gasUsed.sub(teardownGasLimits).add(this.teardownGasUsed);
}

/**
* The gasUsed as if the entire teardown gas limit was consumed.
*/
getGasUsedForFee(): Gas {
return this.gasUsed;
return this.getTotalGasUsed().sub(teardownGasLimits).add(this.teardownGasUsed);
}

/**
Expand All @@ -284,10 +293,10 @@ export class PublicTxContext {
* Should only be called during or after teardown.
*/
private getTransactionFeeUnsafe(): Fr {
const txFee = this.gasUsed.computeFee(this.globalVariables.gasFees);
const txFee = this.getTotalGasUsed().computeFee(this.globalVariables.gasFees);
this.log.debug(`Computed tx fee`, {
txFee,
gasUsed: inspect(this.gasUsed),
gasUsed: inspect(this.getTotalGasUsed()),
gasFees: inspect(this.globalVariables.gasFees),
});
return txFee;
Expand Down Expand Up @@ -320,15 +329,15 @@ export class PublicTxContext {
this.trace,
this.globalVariables,
this.startStateReference,
this.startGasUsed,
/*startGasUsed=*/ this.gasUsedByPrivate,
this.gasSettings,
this.setupCallRequests,
this.appLogicCallRequests,
this.teardownCallRequests,
this.nonRevertibleAccumulatedDataFromPrivate,
this.revertibleAccumulatedDataFromPrivate,
endTreeSnapshots,
/*endGasUsed=*/ this.gasUsed,
/*endGasUsed=*/ this.getTotalGasUsed(),
this.getTransactionFeeUnsafe(),
this.revertCode,
);
Expand Down Expand Up @@ -401,3 +410,13 @@ class PhaseStateManager {
this.currentlyActiveStateManager = undefined;
}
}

/**
* Apply L2 gas maximum.
*/
function applyMaxToAvailableGas(availableGas: Gas) {
return new Gas(
/*daGas=*/ availableGas.daGas,
/*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_TX_PUBLIC_PORTION),
);
}
21 changes: 4 additions & 17 deletions yarn-project/simulator/src/public/public_tx_simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ import {
UnencryptedFunctionL2Logs,
} from '@aztec/circuit-types';
import { type AvmSimulationStats } from '@aztec/circuit-types/stats';
import {
type Fr,
Gas,
type GlobalVariables,
MAX_L2_GAS_PER_ENQUEUED_CALL,
type PublicCallRequest,
type RevertCode,
} from '@aztec/circuits.js';
import { type Fr, type Gas, type GlobalVariables, type PublicCallRequest, type RevertCode } from '@aztec/circuits.js';
import { type Logger, createLogger } from '@aztec/foundation/log';
import { Timer } from '@aztec/foundation/timer';
import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client';
Expand Down Expand Up @@ -266,23 +259,17 @@ export class PublicTxSimulator {
const address = executionRequest.callContext.contractAddress;
const fnName = await getPublicFunctionDebugName(this.worldStateDB, address, executionRequest.args);

const availableGas = context.getGasLeftForPhase(phase);
// Gas allocated to an enqueued call can be different from the available gas
// if there is more gas available than the max allocation per enqueued call.
const allocatedGas = new Gas(
/*daGas=*/ availableGas.daGas,
/*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_ENQUEUED_CALL),
);
const allocatedGas = context.getGasLeftAtPhase(phase);

const result = await this.simulateEnqueuedCallInternal(
context.state.getActiveStateManager(),
executionRequest,
allocatedGas,
context.getTransactionFee(phase),
/*transactionFee=*/ context.getTransactionFee(phase),
fnName,
);

const gasUsed = allocatedGas.sub(result.gasLeft);
const gasUsed = allocatedGas.sub(result.gasLeft); // by enqueued call
context.consumeGas(phase, gasUsed);
this.log.verbose(
`[AVM] Enqueued public call consumed ${gasUsed.l2Gas} L2 gas ending with ${result.gasLeft.l2Gas} L2 gas left.`,
Expand Down

0 comments on commit db37318

Please sign in to comment.