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

feat(public-vm): avm journal #3945

Merged
merged 23 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 22 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ cmake-build-debug

# Local Netlify folder
.netlify

.graphite*
3 changes: 3 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
describe('Avm', () => {
it('Executes a simple call', () => {});
});
44 changes: 44 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Fr } from '@aztec/foundation/fields';

import { AvmMachineState } from './avm_machine_state.js';
import { AvmMessageCallResult } from './avm_message_call_result.js';
import { AvmStateManager } from './avm_state_manager.js';
import { AvmInterpreter } from './interpreter/index.js';
import { interpretBytecode } from './opcodes/from_bytecode.js';
import { Instruction } from './opcodes/index.js';

/**
* Avm Executor manages the execution of the AVM
*
* It stores a state manager
*/
export class AvmContext {
private stateManager: AvmStateManager;

constructor(stateManager: AvmStateManager) {
this.stateManager = stateManager;
}

/**
* Call a contract with the given calldata
*
* - We get the contract from storage
* - We interpret the bytecode
* - We run the interpreter
*
* @param contractAddress -
* @param calldata -
*/
public call(contractAddress: Fr, calldata: Fr[]): AvmMessageCallResult {
// NOTE: the following is mocked as getPublicBytecode does not exist yet
// const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress);
const bytecode = Buffer.from('0x01000100020003');

const instructions: Instruction[] = interpretBytecode(bytecode);

const context = new AvmMachineState(calldata);
const interpreter = new AvmInterpreter(context, this.stateManager, instructions);

return interpreter.run();
}
}
79 changes: 79 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_machine_state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Fr } from '@aztec/foundation/fields';

/**
* Store's data for an Avm execution frame
*/
export class AvmMachineState {
/** - */
public readonly calldata: Fr[];
private returnData: Fr[];

// TODO: implement tagged memory
/** - */
public memory: Fr[];

/** - */
public pc: number;
/** - */
public callStack: number[];

/**
* Create a new avm context
* @param calldata -
*/
constructor(calldata: Fr[]) {
this.calldata = calldata;
this.returnData = [];
this.memory = [];

this.pc = 0;
this.callStack = [];
}

/**
* Return data must NOT be modified once it is set
* @param returnData -
*/
public setReturnData(returnData: Fr[]) {
this.returnData = returnData;
Object.freeze(returnData);
}

/** - */
public getReturnData(): Fr[] {
return this.returnData;
}

/** -
* @param offset -
*/
public readMemory(offset: number): Fr {
// TODO: check offset is within bounds
return this.memory[offset] ?? Fr.ZERO;
}

/** -
* @param offset -
* @param size -
*/
public readMemoryChunk(offset: number, size: number): Fr[] {
// TODO: bounds -> initialise to 0
return this.memory.slice(offset, offset + size);
}

/** -
* @param offset -
* @param value -
*/
public writeMemory(offset: number, value: Fr): void {
this.memory[offset] = value;
}

/** -
* @param offset -
* @param values -
*/
public writeMemoryChunk(offset: number, values: Fr[]): void {
this.memory.splice(offset, values.length, ...values);
}
}
34 changes: 34 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_message_call_result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Fr } from '@aztec/foundation/fields';

/**
* AVM message call result.
*/
export class AvmMessageCallResult {
/** - */
public readonly reverted: boolean;
/** .- */
public readonly output: Fr[];

constructor(reverted: boolean, output: Fr[]) {
this.reverted = reverted;
this.output = output;
}

/**
* Terminate a call as a success
* @param output - Return data
* @returns instance of AvmMessageCallResult
*/
public static success(output: Fr[]): AvmMessageCallResult {
return new AvmMessageCallResult(false, output);
}

/**
* Terminate a call as a revert
* @param output - Return data ( revert message )
* @returns instance of AvmMessageCallResult
*/
public static revert(output: Fr[]): AvmMessageCallResult {
return new AvmMessageCallResult(true, output);
}
}
45 changes: 45 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_state_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { BlockHeader } from '@aztec/circuits.js';

import { AvmJournal, HostStorage } from './journal/index.js';

/**
* The Avm State Manager is the interpreter's interface to the node's state
* It creates revertible views into the node state and manages the current call's journal
*/
export class AvmStateManager {
/** - */
public readonly blockHeader: BlockHeader;

/**
* Journal keeps track of pending state changes
*/
public readonly journal: AvmJournal;

constructor(blockHeader: BlockHeader, journal: AvmJournal) {
this.blockHeader = blockHeader;
this.journal = journal;
}

/**
* Create a base state root manager
* - this should be created by the highest level item where the state
* can be reverted
* @param blockHeader -
* @param hostStorage - An immutable view into the node db
* @returns Avm State Manager
*/
public static rootStateManager(blockHeader: BlockHeader, hostStorage: HostStorage): AvmStateManager {
const journal = AvmJournal.rootJournal(hostStorage);
return new AvmStateManager(blockHeader, journal);
}

/**
* Avm State
* @param parent - Avm state manager with a forked journal
* @returns
*/
public static forkStateManager(parent: AvmStateManager): AvmStateManager {
const journal = AvmJournal.branchParent(parent.journal);
return new AvmStateManager(parent.blockHeader, journal);
}
}
1 change: 1 addition & 0 deletions yarn-project/acir-simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Place large AVM text fixtures in here
3 changes: 3 additions & 0 deletions yarn-project/acir-simulator/src/avm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './avm_machine_state.js';
export * from './avm_context.js';
export * from './avm_state_manager.js';
1 change: 1 addition & 0 deletions yarn-project/acir-simulator/src/avm/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './interpreter.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Fr } from '@aztec/foundation/fields';

import { mock } from 'jest-mock-extended';

import { AvmMachineState } from '../avm_machine_state.js';
import { AvmStateManager } from '../avm_state_manager.js';
import { Add } from '../opcodes/arithmetic.js';
import { Return } from '../opcodes/control_flow.js';
import { Instruction } from '../opcodes/instruction.js';
import { CallDataCopy } from '../opcodes/memory.js';
import { AvmInterpreter } from './interpreter.js';

describe('interpreter', () => {
it('Should execute a series of instructions', () => {
const calldata: Fr[] = [new Fr(1), new Fr(2)];
const stateManager = mock<AvmStateManager>();

const instructions: Instruction[] = [
// Copy the first two elements of the calldata to memory regions 0 and 1
new CallDataCopy(0, 2, 0),
// Add the two together and store the result in memory region 2
new Add(0, 1, 2), // 1 + 2
// Return the result
new Return(2, 1), // [3]
];

const context = new AvmMachineState(calldata);
const interpreter = new AvmInterpreter(context, stateManager, instructions);
const avmReturnData = interpreter.run();

expect(avmReturnData.reverted).toBe(false);

const returnData = avmReturnData.output;
expect(returnData.length).toBe(1);
expect(returnData).toEqual([new Fr(3)]);
});
});
54 changes: 54 additions & 0 deletions yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// import { AvmContext } from "../avm_machineState.js";
import { Fr } from '@aztec/foundation/fields';

import { AvmMachineState } from '../avm_machine_state.js';
import { AvmMessageCallResult } from '../avm_message_call_result.js';
import { AvmStateManager } from '../avm_state_manager.js';
import { Instruction } from '../opcodes/index.js';

/**
* Avm Interpreter
*
* Executes an Avm context
*/
export class AvmInterpreter {
private instructions: Instruction[] = [];
private machineState: AvmMachineState;
private stateManager: AvmStateManager;

constructor(machineState: AvmMachineState, stateManager: AvmStateManager, bytecode: Instruction[]) {
this.machineState = machineState;
this.stateManager = stateManager;
this.instructions = bytecode;
}

/**
* Run the avm
* @returns bool - successful execution will return true
* - reverted execution will return false
* - any other panic will throw
*/
run(): AvmMessageCallResult {
try {
for (const instruction of this.instructions) {
instruction.execute(this.machineState, this.stateManager);
}

const returnData = this.machineState.getReturnData();
return AvmMessageCallResult.success(returnData);
} catch (e) {
// TODO: This should only accept AVM defined errors, anything else SHOULD be thrown upstream
const revertData = this.machineState.getReturnData();
return AvmMessageCallResult.revert(revertData);
}
}

/**
* Get the return data from avm execution
* TODO: this should fail if the code has not been executed
* - maybe move the return in run into a variable and track it
*/
returnData(): Fr[] {
return this.machineState.getReturnData();
}
}
8 changes: 8 additions & 0 deletions yarn-project/acir-simulator/src/avm/journal/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Error thrown when a base journal is attempted to be merged.
*/
export class RootJournalCannotBeMerged extends Error {
constructor() {
super('Root journal cannot be merged');
}
}
21 changes: 21 additions & 0 deletions yarn-project/acir-simulator/src/avm/journal/host_storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js';

/**
* Host storage
*
* A wrapper around the node dbs
*/
export class HostStorage {
/** - */
public readonly publicStateDb: PublicStateDB;
/** - */
public readonly contractsDb: PublicContractsDB;
/** - */
public readonly commitmentsDb: CommitmentsDB;

constructor(publicStateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) {
this.publicStateDb = publicStateDb;
this.contractsDb = contractsDb;
this.commitmentsDb = commitmentsDb;
}
}
2 changes: 2 additions & 0 deletions yarn-project/acir-simulator/src/avm/journal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './host_storage.js';
export * from './journal.js';
Loading