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: accrued substate instructions #4197

Merged
merged 10 commits into from
Jan 29, 2024
5 changes: 5 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_memory_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ export class TaggedMemory {
return this._mem.slice(offset, offset + size);
}

public getSliceAs<T>(offset: number, size: number): T[] {
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
return this._mem.slice(offset, offset + size) as T[];
}

public getSliceTags(offset: number, size: number): TypeTag[] {
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
return this._mem.slice(offset, offset + size).map(TaggedMemory.getTag);
Expand Down
23 changes: 14 additions & 9 deletions yarn-project/acir-simulator/src/avm/journal/journal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ describe('journal', () => {
describe('UTXOs', () => {
it('Should maintain commitments', () => {
const utxo = new Fr(1);
journal.writeCommitment(utxo);
journal.writeNoteHash(utxo);

const journalUpdates = journal.flush();
expect(journalUpdates.newCommitments).toEqual([utxo]);
expect(journalUpdates.newNoteHashes).toEqual([utxo]);
});

it('Should maintain l1 messages', () => {
const utxo = new Fr(1);
const utxo = [new Fr(1)];
journal.writeL1Message(utxo);

const journalUpdates = journal.flush();
Expand Down Expand Up @@ -123,16 +123,20 @@ describe('journal', () => {
const valueT1 = new Fr(2);
const commitment = new Fr(10);
const commitmentT1 = new Fr(20);
const logs = [new Fr(1), new Fr(2)];
const logsT1 = [new Fr(3), new Fr(4)];

journal.writeStorage(contractAddress, key, value);
journal.writeCommitment(commitment);
journal.writeL1Message(commitment);
journal.writeNoteHash(commitment);
journal.writeLog(logs);
journal.writeL1Message(logs);
journal.writeNullifier(commitment);

const journal1 = new AvmJournal(journal.hostStorage, journal);
journal.writeStorage(contractAddress, key, valueT1);
journal.writeCommitment(commitmentT1);
journal.writeL1Message(commitmentT1);
journal.writeNoteHash(commitmentT1);
journal.writeLog(logsT1);
journal.writeL1Message(logsT1);
journal.writeNullifier(commitmentT1);

journal1.mergeWithParent();
Expand All @@ -143,8 +147,9 @@ describe('journal', () => {

// Check that the UTXOs are merged
const journalUpdates: JournalData = journal.flush();
expect(journalUpdates.newCommitments).toEqual([commitment, commitmentT1]);
expect(journalUpdates.newL1Messages).toEqual([commitment, commitmentT1]);
expect(journalUpdates.newNoteHashes).toEqual([commitment, commitmentT1]);
expect(journalUpdates.newLogs).toEqual([logs, logsT1]);
expect(journalUpdates.newL1Messages).toEqual([logs, logsT1]);
expect(journalUpdates.newNullifiers).toEqual([commitment, commitmentT1]);
});

Expand Down
54 changes: 24 additions & 30 deletions yarn-project/acir-simulator/src/avm/journal/journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import { HostStorage } from './host_storage.js';
* Data held within the journal
*/
export type JournalData = {
newCommitments: Fr[];

newL1Messages: Fr[];

newNoteHashes: Fr[];
newNullifiers: Fr[];
newL1Messages: Fr[][];
newLogs: Fr[][];
/** contract address -\> key -\> value */
storageWrites: Map<bigint, Map<bigint, Fr>>;
};
Expand All @@ -34,11 +33,11 @@ export class AvmJournal {
private storageReads: Map<bigint, Map<bigint, Fr>> = new Map();

// New written state
private newCommitments: Fr[] = [];
private newNoteHashes: Fr[] = [];
private newNullifiers: Fr[] = [];
private newL1Message: Fr[] = [];

// New Substrate
// New Substate
private newL1Messages: Fr[][] = [];
private newLogs: Fr[][] = [];

// contract address -> key -> value
Expand Down Expand Up @@ -102,27 +101,22 @@ export class AvmJournal {
return this.hostStorage.publicStateDb.storageRead(contractAddress, key);
}

/** -
* @param commitment -
*/
public writeCommitment(commitment: Fr) {
this.newCommitments.push(commitment);
public writeNoteHash(noteHash: Fr) {
this.newNoteHashes.push(noteHash);
}

/** -
* @param message -
*/
public writeL1Message(message: Fr) {
this.newL1Message.push(message);
public writeL1Message(message: Fr[]) {
this.newL1Messages.push(message);
}

/** -
* @param nullifier -
*/
public writeNullifier(nullifier: Fr) {
this.newNullifiers.push(nullifier);
}

public writeLog(log: Fr[]) {
this.newLogs.push(log);
}

/**
* Merge Journal into parent
* - Utxo objects are concatenated
Expand All @@ -133,26 +127,26 @@ export class AvmJournal {
throw new RootJournalCannotBeMerged();
}

const incomingFlush = this.flush();

// Merge UTXOs
this.parentJournal.newCommitments = this.parentJournal.newCommitments.concat(incomingFlush.newCommitments);
this.parentJournal.newL1Message = this.parentJournal.newL1Message.concat(incomingFlush.newL1Messages);
this.parentJournal.newNullifiers = this.parentJournal.newNullifiers.concat(incomingFlush.newNullifiers);
this.parentJournal.newNoteHashes = this.parentJournal.newNoteHashes.concat(this.newNoteHashes);
this.parentJournal.newL1Messages = this.parentJournal.newL1Messages.concat(this.newL1Messages);
this.parentJournal.newNullifiers = this.parentJournal.newNullifiers.concat(this.newNullifiers);

// Merge Public State
mergeContractMaps(this.parentJournal.storageWrites, incomingFlush.storageWrites);
mergeContractMaps(this.parentJournal.storageWrites, this.storageWrites);
}

/** Access the current state of the journal
/**
* Access the current state of the journal
*
* @returns a JournalData object that can be used to write to the storage
* @returns a JournalData object
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

[Re: lines 141 to 141]

Please fix comment.

See this comment inline on Graphite.

public flush(): JournalData {
return {
newCommitments: this.newCommitments,
newL1Messages: this.newL1Message,
newNoteHashes: this.newNoteHashes,
newNullifiers: this.newNullifiers,
newL1Messages: this.newL1Messages,
newLogs: this.newLogs,
storageWrites: this.storageWrites,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { mock } from 'jest-mock-extended';

import { AvmMachineState } from '../avm_machine_state.js';
import { Field } from '../avm_memory_types.js';
import { initExecutionEnvironment } from '../fixtures/index.js';
import { HostStorage } from '../journal/host_storage.js';
import { AvmJournal } from '../journal/journal.js';
import { EmitNoteHash, EmitNullifier, EmitUnencryptedLog, SendL2ToL1Message } from './accrued_substate.js';
import { StaticCallStorageAlterError } from './storage.js';

describe('Accrued Substate', () => {
let journal: AvmJournal;
let machineState: AvmMachineState;

beforeEach(() => {
const hostStorage = mock<HostStorage>();
journal = new AvmJournal(hostStorage);
machineState = new AvmMachineState(initExecutionEnvironment());
});

it('Should append a new note hash correctly', async () => {
const value = new Field(69n);
machineState.memory.set(0, value);

await new EmitNoteHash(0).execute(machineState, journal);

const journalState = journal.flush();
expect(journalState.newNoteHashes).toEqual([value]);
});

it('Should append a new nullifier correctly', async () => {
const value = new Field(69n);
machineState.memory.set(0, value);

await new EmitNullifier(0).execute(machineState, journal);

const journalState = journal.flush();
expect(journalState.newNullifiers).toEqual([value]);
});

it('Should append unencrypted logs correctly', async () => {
const startOffset = 0;

const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)];
machineState.memory.setSlice(0, values);

const length = values.length;

await new EmitUnencryptedLog(startOffset, length).execute(machineState, journal);

const journalState = journal.flush();
expect(journalState.newLogs).toEqual([values]);
});

it('Should append l1 to l2 messages correctly', async () => {
const startOffset = 0;

const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)];
machineState.memory.setSlice(0, values);

const length = values.length;

await new SendL2ToL1Message(startOffset, length).execute(machineState, journal);

const journalState = journal.flush();
expect(journalState.newLogs).toEqual([values]);
});

it('All substate instructions should fail within a static call', async () => {
const executionEnvironment = initExecutionEnvironment({ isStaticCall: true });
machineState = new AvmMachineState(executionEnvironment);

const instructions = [
new EmitNoteHash(0),
new EmitNullifier(0),
new EmitUnencryptedLog(0, 1),
new SendL2ToL1Message(0, 1),
];

for (const instruction of instructions) {
const inst = () => instruction.execute(machineState, journal);
await expect(inst()).rejects.toThrowError(StaticCallStorageAlterError);
}
});
});
101 changes: 101 additions & 0 deletions yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Fr } from '@aztec/foundation/fields';

import { AvmMachineState } from '../avm_machine_state.js';
import { TypeTag } from '../avm_memory_types.js';
import { AvmJournal } from '../journal/journal.js';
import { Instruction } from './instruction.js';
import { StaticCallStorageAlterError } from './storage.js';

export class EmitNoteHash extends Instruction {
static type: string = 'EMITNOTEHASH';
static numberOfOperands = 1;

constructor(private noteHashOffset: number) {
super();
}

async execute(machineState: AvmMachineState, journal: AvmJournal): Promise<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

Instruction.checkTags(machineState, TypeTag.FIELD, this.noteHashOffset);
Copy link
Member Author

@Maddiaa0 Maddiaa0 Jan 29, 2024

Choose a reason for hiding this comment

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

I am checking the tag to make sure the cast below is safe. As at the moment we only want to emit sets of field elements

Copy link
Contributor

Choose a reason for hiding this comment

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

Might be fine to just say "any type can get put into storage/notehash/nullifier/log trace" and they just get upcast to field. But I also think it's fine to just say that they all must be fields.

If we don't update the YP to say that these instructions enforce input is a field, then we should create a ticket to align the YP with this one way or the other.

Copy link
Member Author

Choose a reason for hiding this comment

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

i remove the check this makes sense

const noteHash = machineState.memory.getAs<Fr>(this.noteHashOffset);

journal.writeNoteHash(noteHash);

this.incrementPc(machineState);
}
}

export class EmitNullifier extends Instruction {
static type: string = 'EMITNULLIFIER';
static numberOfOperands = 1;

constructor(private nullifierOffset: number) {
super();
}

async execute(machineState: AvmMachineState, journal: AvmJournal): Promise<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

Instruction.checkTags(machineState, TypeTag.FIELD, this.nullifierOffset);
const nullifier = machineState.memory.getAs<Fr>(this.nullifierOffset);

journal.writeNullifier(nullifier);

this.incrementPc(machineState);
}
}

export class EmitUnencryptedLog extends Instruction {
static type: string = 'EMITUNENCRYPTEDLOG';
static numberOfOperands = 2;

constructor(private logOffset: number, private logSize: number) {
super();
}

async execute(machineState: AvmMachineState, journal: AvmJournal): Promise<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

// Check log tags are all fields
const offsets = Array.from({ length: this.logSize }, (_, i) => this.logOffset + i);
Instruction.checkTags(machineState, TypeTag.FIELD, ...offsets);

const log = machineState.memory.getSliceAs<Fr>(this.logOffset, this.logSize);

journal.writeLog(log);

this.incrementPc(machineState);
}
}

export class SendL2ToL1Message extends Instruction {
static type: string = 'EMITUNENCRYPTEDLOG';
static numberOfOperands = 2;

constructor(private msgOffset: number, private msgSize: number) {
super();
}

async execute(machineState: AvmMachineState, journal: AvmJournal): Promise<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

// Check log tags are all fields
const offsets = Array.from({ length: this.msgSize }, (_, i) => this.msgOffset + i);
Instruction.checkTags(machineState, TypeTag.FIELD, ...offsets);

const msg = machineState.memory.getSliceAs<Fr>(this.msgOffset, this.msgSize);

journal.writeLog(msg);

this.incrementPc(machineState);
}
}
Loading