Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(avm-simulator): avm's nested calls now stay internal and properly track PublicExecutionResult #6165

Merged
merged 1 commit into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions yarn-project/end-to-end/src/e2e_avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ describe('e2e_avm_simulator', () => {
expect(await avmContract.methods.call_avm_from_acvm().simulate()).toEqual(123456n);
});

it('Can call ACVM function from AVM', async () => {
it.skip('Can call ACVM function from AVM', async () => {
expect(await avmContract.methods.call_acvm_from_avm().simulate()).toEqual(123456n);
});

Expand All @@ -113,7 +113,7 @@ describe('e2e_avm_simulator', () => {
await avmContract.methods.assert_unsiloed_nullifier_acvm(nullifier).send().wait();
});

it('AVM nested call to ACVM sees settled nullifiers', async () => {
it.skip('AVM nested call to ACVM sees settled nullifiers', async () => {
const nullifier = new Fr(123456);
await avmContract.methods.new_nullifier(nullifier).send().wait();
await avmContract.methods
Expand All @@ -122,7 +122,7 @@ describe('e2e_avm_simulator', () => {
.wait();
});

describe('Authwit', () => {
describe.skip('Authwit', () => {
it('Works if authwit provided', async () => {
const recipient = AztecAddress.random();
const action = avmContract.methods.test_authwit_send_money(
Expand Down Expand Up @@ -194,8 +194,7 @@ describe('e2e_avm_simulator', () => {
expect(tx.status).toEqual(TxStatus.MINED);
});

// TODO(4293): this should work! Fails in public kernel because both nullifiers are incorrectly being siloed by same address
it.skip('Should be able to emit the same unsiloed nullifier from two different contracts', async () => {
it('Should be able to emit the same unsiloed nullifier from two different contracts', async () => {
const nullifier = new Fr(1);
const tx = await avmContract.methods
.create_same_nullifier_in_nested_call(secondAvmContract.address, nullifier)
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/simulator/src/avm/avm_simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';

import { strict as assert } from 'assert';

import { isAvmBytecode } from '../public/transitional_adaptors.js';
import type { AvmContext } from './avm_context.js';
import { AvmContractCallResults } from './avm_message_call_result.js';
import { AvmExecutionError, InvalidProgramCounterError, NoBytecodeForContractError } from './errors.js';
Expand Down Expand Up @@ -32,6 +33,7 @@ export class AvmSimulator {
if (!bytecode) {
throw new NoBytecodeForContractError(this.context.environment.address);
}
assert(isAvmBytecode(bytecode), "AVM simulator can't execute non-AVM bytecode");

return await this.executeBytecode(bytecode);
}
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/simulator/src/avm/journal/journal.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// TODO(5818): Rename file and all uses of "journal"
import { UnencryptedL2Log } from '@aztec/circuit-types';
import {
AztecAddress,
Expand Down Expand Up @@ -30,6 +31,7 @@ import {
type TracedUnencryptedL2Log,
} from './trace_types.js';

// TODO:(5818): do we need this type anymore?
/**
* Data held within the journal
*/
Expand Down Expand Up @@ -81,7 +83,7 @@ export class AvmPersistableStateManager {
/** Reference to node storage */
public readonly hostStorage: HostStorage;

// TODO: make members private once this is not used in transitional_adaptors.ts.
// TODO(5818): make members private once this is not used in transitional_adaptors.ts.
/** World State */
/** Public storage, including cached writes */
public publicStorage: PublicStorage;
Expand Down Expand Up @@ -327,6 +329,7 @@ export class AvmPersistableStateManager {
this.trace.acceptAndMerge(nestedJournal.trace);
}

// TODO:(5818): do we need this type anymore?
/**
* Access the current state of the journal
*
Expand Down
1 change: 1 addition & 0 deletions yarn-project/simulator/src/avm/journal/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class WorldStateAccessTrace {

constructor(parentTrace?: WorldStateAccessTrace) {
this.accessCounter = parentTrace ? parentTrace.accessCounter : 0;
// TODO(4805): consider tracking the parent's trace vector lengths so we can enforce limits
}

public getAccessCounter() {
Expand Down
35 changes: 19 additions & 16 deletions yarn-project/simulator/src/avm/opcodes/external_calls.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { FunctionSelector } from '@aztec/circuits.js';
import { FunctionSelector, Gas } from '@aztec/circuits.js';
import { padArrayEnd } from '@aztec/foundation/collection';

import { executePublicFunction } from '../../public/executor.js';
import {
convertPublicExecutionResult,
createPublicExecutionContext,
updateAvmContextFromPublicExecutionResult,
} from '../../public/transitional_adaptors.js';
import { convertAvmResultsToPxResult, createPublicExecution } from '../../public/transitional_adaptors.js';
import type { AvmContext } from '../avm_context.js';
import { gasLeftToGas, sumGas } from '../avm_gas.js';
import { Field, Uint8 } from '../avm_memory_types.js';
import { type AvmContractCallResults } from '../avm_message_call_result.js';
import { AvmSimulator } from '../avm_simulator.js';
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
import { Addressing } from './addressing_mode.js';
import { Instruction } from './instruction.js';
Expand Down Expand Up @@ -69,21 +65,29 @@ abstract class ExternalCall extends Instruction {
const totalGas = sumGas(this.gasCost(memoryOperations), allocatedGas);
context.machineState.consumeGas(totalGas);

// TRANSITIONAL: This should be removed once the AVM is fully operational and the public executor is gone.
// TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit
const nestedContext = context.createNestedContractCallContext(
callAddress.toFr(),
calldata,
allocatedGas,
this.type,
FunctionSelector.fromField(functionSelector),
);
const pxContext = createPublicExecutionContext(nestedContext, calldata);
const pxResults = await executePublicFunction(pxContext, /*nested=*/ true);
const startSideEffectCounter = nestedContext.persistableState.trace.accessCounter;

const oldStyleExecution = createPublicExecution(startSideEffectCounter, nestedContext.environment, calldata);
const nestedCallResults: AvmContractCallResults = await new AvmSimulator(nestedContext).execute();
const pxResults = convertAvmResultsToPxResult(
nestedCallResults,
startSideEffectCounter,
oldStyleExecution,
Gas.from(allocatedGas),
nestedContext,
);
// store the old PublicExecutionResult object to maintain a recursive data structure for the old kernel
context.persistableState.transitionalExecutionResult.nestedExecutions.push(pxResults);
const nestedCallResults: AvmContractCallResults = convertPublicExecutionResult(pxResults);
updateAvmContextFromPublicExecutionResult(nestedContext, pxResults);
const nestedPersistableState = nestedContext.persistableState;
// END TRANSITIONAL

// const nestedContext = context.createNestedContractCallContext(
// callAddress.toFr(),
// calldata,
Expand All @@ -92,7 +96,6 @@ abstract class ExternalCall extends Instruction {
// FunctionSelector.fromField(functionSelector),
// );
// const nestedCallResults: AvmContractCallResults = await new AvmSimulator(nestedContext).execute();
// const nestedPersistableState = nestedContext.persistableState;

const success = !nestedCallResults.reverted;

Expand All @@ -114,9 +117,9 @@ abstract class ExternalCall extends Instruction {

// TODO: Should we merge the changes from a nested call in the case of a STATIC call?
if (success) {
context.persistableState.acceptNestedCallState(nestedPersistableState);
context.persistableState.acceptNestedCallState(nestedContext.persistableState);
} else {
context.persistableState.rejectNestedCallState(nestedPersistableState);
context.persistableState.rejectNestedCallState(nestedContext.persistableState);
}

memory.assert(memoryOperations);
Expand Down
35 changes: 25 additions & 10 deletions yarn-project/simulator/src/public/executor.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC now the ACVM can call the AVM. Do you expect to support that and will it work ok, or should we rather just remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I've really messed with any of the interop stuff in that direction, so it might work 🤷‍♂️ Maybe let's see how it goes when we start changing contracts to use the AVM and then we can remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(i didn't disable the e2e interop tests in that direction)

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { PackedValuesCache } from '../common/packed_values_cache.js';
import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from './db.js';
import { type PublicExecution, type PublicExecutionResult, checkValidStaticCall } from './execution.js';
import { PublicExecutionContext } from './public_execution_context.js';
import { convertAvmResults, createAvmExecutionEnvironment, isAvmBytecode } from './transitional_adaptors.js';
import { convertAvmResultsToPxResult, createAvmExecutionEnvironment, isAvmBytecode } from './transitional_adaptors.js';

/**
* Execute a public function and return the execution result.
Expand All @@ -46,15 +46,22 @@ export async function executePublicFunction(
}

if (isAvmBytecode(bytecode)) {
return await executePublicFunctionAvm(context);
return await executeTopLevelPublicFunctionAvm(context);
} else {
return await executePublicFunctionAcvm(context, bytecode, nested);
}
}

async function executePublicFunctionAvm(executionContext: PublicExecutionContext): Promise<PublicExecutionResult> {
/**
* Execute a top-level public function call (the first call in an enqueued-call/execution-request) in the AVM.
* Translate the results back to the PublicExecutionResult format.
*/
async function executeTopLevelPublicFunctionAvm(
executionContext: PublicExecutionContext,
): Promise<PublicExecutionResult> {
const address = executionContext.execution.contractAddress;
const selector = executionContext.execution.functionData.selector;
const startGas = executionContext.availableGas;
const log = createDebugLogger('aztec:simulator:public_execution');
log.verbose(`[AVM] Executing public external function ${address.toString()}:${selector}.`);

Expand All @@ -65,7 +72,12 @@ async function executePublicFunctionAvm(executionContext: PublicExecutionContext
executionContext.contractsDb,
executionContext.commitmentsDb,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Re: line 55]

Maybe add a comment clarifying that this function is (as opposed to ...Acvm) only called at the top level but not for nested calls.

See this comment inline on Graphite.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do (in the next PR though where that is actually made true)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was confused, that's this PR 👍

);

// TODO(6207): add sideEffectCounter to persistableState construction
// or modify the PersistableStateManager to manage rollbacks across enqueued-calls and transactions.
const worldStateJournal = new AvmPersistableStateManager(hostStorage);
const startSideEffectCounter = executionContext.execution.callContext.sideEffectCounter;
worldStateJournal.trace.accessCounter = startSideEffectCounter;

const executionEnv = createAvmExecutionEnvironment(
executionContext.execution,
Expand All @@ -75,18 +87,21 @@ async function executePublicFunctionAvm(executionContext: PublicExecutionContext
executionContext.transactionFee,
);

const machineState = new AvmMachineState(executionContext.availableGas);
const context = new AvmContext(worldStateJournal, executionEnv, machineState);
const simulator = new AvmSimulator(context);
const machineState = new AvmMachineState(startGas);
const avmContext = new AvmContext(worldStateJournal, executionEnv, machineState);
const simulator = new AvmSimulator(avmContext);

const result = await simulator.execute();
const newWorldState = context.persistableState.flush();
const avmResult = await simulator.execute();

log.verbose(
`[AVM] ${address.toString()}:${selector} returned, reverted: ${result.reverted}, reason: ${result.revertReason}.`,
`[AVM] ${address.toString()}:${selector} returned, reverted: ${avmResult.reverted}, reason: ${
avmResult.revertReason
}.`,
);

return await convertAvmResults(executionContext, newWorldState, result, machineState);
return Promise.resolve(
convertAvmResultsToPxResult(avmResult, startSideEffectCounter, executionContext.execution, startGas, avmContext),
);
}

async function executePublicFunctionAcvm(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class PublicExecutionContext extends TypedOracle {
public readonly header: Header,
public readonly globalVariables: GlobalVariables,
private readonly packedValuesCache: PackedValuesCache,
private readonly sideEffectCounter: SideEffectCounter,
// TRANSITIONAL: once AVM-ACVM interoperability is removed (fully functional AVM), sideEffectCounter can be made private
public readonly sideEffectCounter: SideEffectCounter,
public readonly stateDb: PublicStateDB,
public readonly contractsDb: PublicContractsDB,
public readonly commitmentsDb: CommitmentsDB,
Expand Down
Loading
Loading