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

940 - getNotes filter nullified notes #1186

Merged
merged 5 commits into from
Jul 27, 2023
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
65 changes: 57 additions & 8 deletions yarn-project/acir-simulator/src/client/client_execution_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
toAcvmL1ToL2MessageLoadOracleInputs,
} from '../acvm/index.js';
import { PackedArgsCache } from '../packed_args_cache.js';
import { DBOracle, NoteData } from './db_oracle.js';
import { DBOracle, PendingNoteData } from './db_oracle.js';
import { pickNotes } from './pick_notes.js';

/**
Expand All @@ -34,8 +34,12 @@ export class ClientTxExecutionContext {
public historicRoots: PrivateHistoricTreeRoots,
/** The cache of packed arguments */
public packedArgsCache: PackedArgsCache,
/** Pending commitments created (and not nullified) up to current point in execution **/
private pendingNotes: NoteData[] = [],
/** Pending notes created (and not nullified) up to current point in execution.
* If a nullifier for a note in this list is emitted, the note will be REMOVED. */
private pendingNotes: PendingNoteData[] = [],
/** The list of nullifiers created in this transaction. The commitment/note which is nullified
* might be pending or not (i.e., was generated in a previous transaction) */
private pendingNullifiers: Set<Fr> = new Set<Fr>(),
) {}

/**
Expand All @@ -50,6 +54,7 @@ export class ClientTxExecutionContext {
this.historicRoots,
this.packedArgsCache,
this.pendingNotes,
this.pendingNullifiers,
);
}

Expand Down Expand Up @@ -114,14 +119,17 @@ export class ClientTxExecutionContext {
): Promise<ACVMField[]> {
const storageSlotField = fromACVMField(storageSlot);

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/920): don't 'get' notes nullified in pendingNullifiers
const pendingNotes = this.pendingNotes.filter(
n => n.contractAddress.equals(contractAddress) && n.storageSlot.equals(storageSlotField),
);

const dbNotes = await this.db.getNotes(contractAddress, storageSlotField);

const notes = pickNotes([...pendingNotes, ...dbNotes], {
// Remove notes which were already nullified during this transaction.
const dbNotesFiltered = dbNotes.filter(n => !this.pendingNullifiers.has(n.nullifier as Fr));

// Nullified pending notes are already removed from the list.
const notes = pickNotes([...dbNotesFiltered, ...pendingNotes], {
sortBy: sortBy.map(field => +field),
sortOrder: sortOrder.map(field => +field),
limit,
Expand Down Expand Up @@ -171,15 +179,56 @@ export class ClientTxExecutionContext {
* @param contractAddress - The contract address.
* @param storageSlot - The storage slot.
* @param preimage - new note preimage.
* @param nullifier - note nullifier
* @param innerNoteHash - inner note hash
*/
public async pushNewNote(contractAddress: AztecAddress, storageSlot: ACVMField, preimage: ACVMField[]) {
public async pushNewNote(contractAddress: AztecAddress, storageSlot: Fr, preimage: Fr[], innerNoteHash: Fr) {
const wasm = await CircuitsWasm.get();
const nonce = computeCommitmentNonce(wasm, this.txNullifier, this.pendingNotes.length);
this.pendingNotes.push({
contractAddress,
storageSlot: fromACVMField(storageSlot),
storageSlot: storageSlot,
nonce,
preimage: preimage.map(f => fromACVMField(f)),
preimage,
innerNoteHash,
});
}

/**
* Adding a nullifier into the current set of all pending nullifiers created
* within the current transaction/execution.
* @param nullifier - The pending nullifier to add in the list.
*/
public pushPendingNullifier(nullifier: Fr) {
this.pendingNullifiers.add(nullifier);
}

/**
* Update the list of pending notes by chopping a note which is nullified.
* The identifier used to determine matching is the inner note hash value.
* However, we adopt a defensive approach and ensure that the contract address
* and storage slot do match.
* Note that this method might be called with an innerNoteHash referring to
* a note created in a previous transaction which will result in this array
* of pending notes left unchanged.
* @param innerNoteHash - The inner note hash value to which note will be chopped.
* @param contractAddress - The contract address
* @param storageSlot - The storage slot as a Field Fr element
*/
public nullifyPendingNotes(innerNoteHash: Fr, contractAddress: AztecAddress, storageSlot: Fr) {
// IMPORTANT: We do need an in-place array mutation of this.pendingNotes as this array is shared
// by reference among the nested calls. That is the main reason for 'splice' usage below.
this.pendingNotes.splice(
0,
this.pendingNotes.length,
...this.pendingNotes.filter(
n =>
!(
n.innerNoteHash.equals(innerNoteHash) &&
n.contractAddress.equals(contractAddress) &&
n.storageSlot.equals(storageSlot)
),
),
);
}
Comment on lines +207 to +233
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice! As a followup task, we can probably replace the PrivateFunctionExecution's newNotes and then clean up this chunk of code in the kernel prover

}
10 changes: 10 additions & 0 deletions yarn-project/acir-simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,20 @@ export interface NoteData {
nonce: Fr;
/** The preimage of the note */
preimage: Fr[];
/** The corresponding nullifier of the note */
nullifier?: Fr;
/** The note's leaf index in the private data tree. Undefined for pending notes. */
index?: bigint;
}

/**
* Information about a note needed during execution.
*/
export interface PendingNoteData extends NoteData {
/** The inner note hash (used as a nullified commitment). */
innerNoteHash: Fr;
}

/**
* The format that noir uses to get L1 to L2 Messages.
*/
Expand Down
12 changes: 10 additions & 2 deletions yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe('Private Execution test suite', () => {
const buildNote = (amount: bigint, owner: AztecAddress, storageSlot = Fr.random()) => {
const nonce = new Fr(currentNoteIndex);
const preimage = [new Fr(amount), owner.toField(), Fr.random(), new Fr(1n)];
return { contractAddress, storageSlot, index: currentNoteIndex++, nonce, preimage };
return { contractAddress, storageSlot, index: currentNoteIndex++, nonce, nullifier: new Fr(0), preimage };
};

beforeEach(async () => {
Expand Down Expand Up @@ -359,6 +359,7 @@ describe('Private Execution test suite', () => {
storageSlot,
nonce,
preimage: [new Fr(amount), secret],
nullifier: new Fr(0),
index: 1n,
},
]);
Expand Down Expand Up @@ -624,13 +625,15 @@ describe('Private Execution test suite', () => {
)!;
const insertAbi = PendingCommitmentsContractAbi.functions.find(f => f.name === 'insert_note')!;
const getThenNullifyAbi = PendingCommitmentsContractAbi.functions.find(f => f.name === 'get_then_nullify_note')!;
const getZeroAbi = PendingCommitmentsContractAbi.functions.find(f => f.name === 'get_note_zero_balance')!;

const insertFnSelector = generateFunctionSelector(insertAbi.name, insertAbi.parameters);
const getThenNullifyFnSelector = generateFunctionSelector(getThenNullifyAbi.name, getThenNullifyAbi.parameters);
const getZeroFnSelector = generateFunctionSelector(getZeroAbi.name, getZeroAbi.parameters);

oracle.getPortalContractAddress.mockImplementation(() => Promise.resolve(EthAddress.ZERO));

const args = [amountToTransfer, owner, insertFnSelector, getThenNullifyFnSelector];
const args = [amountToTransfer, owner, insertFnSelector, getThenNullifyFnSelector, getZeroFnSelector];
const result = await runSimulator({
args: args,
abi: abi,
Expand All @@ -640,6 +643,7 @@ describe('Private Execution test suite', () => {

const execInsert = result.nestedExecutions[0];
const execGetThenNullify = result.nestedExecutions[1];
const getNotesAfterNullify = result.nestedExecutions[2];

expect(execInsert.preimages.newNotes).toHaveLength(1);
const note = execInsert.preimages.newNotes[0];
Expand Down Expand Up @@ -668,6 +672,10 @@ describe('Private Execution test suite', () => {
expect(nullifier).toEqual(
await acirSimulator.computeNullifier(contractAddress, nonce, note.storageSlot, note.preimage),
);

// check that the last get_notes call return no note
const afterNullifyingNoteValue = getNotesAfterNullify.callStackItem.publicInputs.returnValues[0].value;
expect(afterNullifyingNoteValue).toEqual(0n);
});

it('cant read a commitment that is inserted later in same call', async () => {
Expand Down
14 changes: 10 additions & 4 deletions yarn-project/acir-simulator/src/client/private_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,13 @@ export class PrivateFunctionExecution {
getNotes: ([slot], sortBy, sortOrder, [limit], [offset], [returnSize]) =>
this.context.getNotes(this.contractAddress, slot, sortBy, sortOrder, +limit, +offset, +returnSize),
getRandomField: () => Promise.resolve(toACVMField(Fr.random())),
notifyCreatedNote: async ([storageSlot], preimage) => {
await this.context.pushNewNote(this.contractAddress, storageSlot, preimage);
notifyCreatedNote: async ([storageSlot], preimage, [innerNoteHash]) => {
await this.context.pushNewNote(
this.contractAddress,
fromACVMField(storageSlot),
preimage.map(f => fromACVMField(f)),
fromACVMField(innerNoteHash),
);

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1040): remove newNotePreimages
// as it is redundant with pendingNoteData. Consider renaming pendingNoteData->pendingNotePreimages.
Expand All @@ -87,13 +92,14 @@ export class PrivateFunctionExecution {
});
return ZERO_ACVM_FIELD;
},
notifyNullifiedNote: ([slot], [nullifier], acvmPreimage) => {
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/920): track list of pendingNullifiers similar to pendingNotes
notifyNullifiedNote: ([slot], [nullifier], acvmPreimage, [innerNoteHash]) => {
newNullifiers.push({
preimage: acvmPreimage.map(f => fromACVMField(f)),
storageSlot: fromACVMField(slot),
nullifier: fromACVMField(nullifier),
});
this.context.pushPendingNullifier(fromACVMField(nullifier));
this.context.nullifyPendingNotes(fromACVMField(innerNoteHash), this.contractAddress, fromACVMField(slot));
return Promise.resolve(ZERO_ACVM_FIELD);
},
callPrivateFunction: async ([acvmContractAddress], [acvmFunctionSelector], [acvmArgsHash]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe('Unconstrained Execution test suite', () => {
storageSlot: Fr.random(),
nonce: Fr.random(),
preimage,
nullifier: Fr.random(),
index: BigInt(index),
})),
);
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/aztec-rpc/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ export class SimulatorOracle implements DBOracle {
*/
async getNotes(contractAddress: AztecAddress, storageSlot: Fr) {
const noteDaos = await this.db.getNoteSpendingInfo(contractAddress, storageSlot);
return noteDaos.map(({ contractAddress, storageSlot, nonce, notePreimage, index }) => ({
return noteDaos.map(({ contractAddress, storageSlot, nonce, notePreimage, nullifier, index }) => ({
contractAddress,
storageSlot,
nonce,
preimage: notePreimage.items,
nullifier,
// RPC Client can use this index to get full MembershipWitness
index,
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('e2e_pending_commitments_contract', () => {
owner,
Fr.fromBuffer(deployedContract.methods.insert_note.selector),
Fr.fromBuffer(deployedContract.methods.get_then_nullify_note.selector),
Fr.fromBuffer(deployedContract.methods.get_note_zero_balance.selector),
)
.send({ origin: owner });

Expand All @@ -77,4 +78,6 @@ describe('e2e_pending_commitments_contract', () => {
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/892): test expected kernel failures if transient reads (or their hints) don't match
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/836): test expected kernel failures if nullifiers (or their hints) don't match
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/839): test creation, getting, nullifying of multiple notes
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1242): test nullifying a note created in a previous transaction and
// get_notes in the same transaction should not return it.
});
Loading