Skip to content

Commit 1f6248d

Browse files
authored
feat: merge-train/fairies (#23775)
BEGIN_COMMIT_OVERRIDE refactor(pxe): type EphemeralArray<T> end-to-end via registry (#23649) END_COMMIT_OVERRIDE
2 parents 8c510af + 0c9a798 commit 1f6248d

11 files changed

Lines changed: 302 additions & 139 deletions

File tree

yarn-project/pxe/src/contract_function_simulator/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,18 @@ export { executePrivateFunction, extractPrivateCircuitPublicInputs } from './ora
1212
export { generateSimulatedProvingResult } from './contract_function_simulator.js';
1313
export { packAsHintedNote } from './oracle/note_packing_utils.js';
1414
export { BoundedVec } from './noir-structs/bounded_vec.js';
15+
export { EphemeralArray } from './noir-structs/ephemeral_array.js';
1516
export { Option } from './noir-structs/option.js';
1617
export { UtilityContext } from './noir-structs/utility_context.js';
18+
export {
19+
EPHEMERAL_ARRAY,
20+
EVENT_VALIDATION_REQUEST,
21+
FIELD,
22+
LOG_RETRIEVAL_REQUEST,
23+
LOG_RETRIEVAL_RESPONSE,
24+
MESSAGE_CONTEXT,
25+
NOTE_VALIDATION_REQUEST,
26+
OPTION,
27+
PENDING_TAGGED_LOG,
28+
POINT,
29+
} from './oracle/oracle_registry.js';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { Fr } from '@aztec/foundation/curves/bn254';
2+
import { FieldReader } from '@aztec/foundation/serialize';
3+
4+
import type { EphemeralArrayService } from '../ephemeral_array_service.js';
5+
import type { TypeMapping } from '../oracle/oracle_registry.js';
6+
7+
/**
8+
* TypeScript counterpart of Noir's `EphemeralArray<T>`.
9+
*
10+
* An ephemeral array looks very different depending on whether it's being produced (as an oracle return value) or
11+
* consumed (as an oracle parameter), so we use a discriminated state to model both without leaking those details to
12+
* callers:
13+
*
14+
* - As a return value (`output` mode), what we have are the typed values the oracle just computed. The slot is just
15+
* an implementation detail of how those values get handed back to ACVM, so we hold onto the values and the service
16+
* they will eventually live in, and only materialize a slot when something actually asks for it.
17+
*
18+
* - As a parameter (`input` mode), what we have is a slot pointing at fields ACVM already wrote into the service,
19+
* plus the TypeMapping that knows how to interpret those fields. We hold onto both so callers can later ask for
20+
* the typed values without having to think about deserialization.
21+
*
22+
* In both modes the idea is the same: store everything we already know at construction time, so the rest of the
23+
* code can work with typed values and never touch slots, services, or TypeMappings directly.
24+
*/
25+
export class EphemeralArray<T> {
26+
private constructor(private readonly state: EphemeralArrayState<T>) {}
27+
28+
/** Create an output-mode array carrying typed values. The service is only used if/when the slot is materialized. */
29+
static fromValues<T>(service: EphemeralArrayService, values: T[]): EphemeralArray<T> {
30+
return new EphemeralArray({ kind: 'output', service, values });
31+
}
32+
33+
/** Wrap an existing slot. Intended for the EPHEMERAL_ARRAY combinator during ACVM deserialization. */
34+
static fromSlot<T>(slot: Fr, mapping: TypeMapping<T>): EphemeralArray<T> {
35+
return new EphemeralArray({ kind: 'input', slot, mapping });
36+
}
37+
38+
/**
39+
* Returns the slot, materializing values into the service on first call for output-mode arrays.
40+
* Intended for the EPHEMERAL_ARRAY combinator during ACVM serialization.
41+
*/
42+
materializeSlot(serializeItem: (v: T) => Fr[]): Fr {
43+
if (this.state.kind === 'input') {
44+
return this.state.slot;
45+
}
46+
if (this.state.cachedSlot === undefined) {
47+
this.state.cachedSlot = this.state.service.newArray(this.state.values.map(serializeItem));
48+
}
49+
return this.state.cachedSlot;
50+
}
51+
52+
/** Read all elements: returns stored values for output-mode arrays, deserializes from the service for input-mode. */
53+
readAll(service: EphemeralArrayService): T[] {
54+
if (this.state.kind === 'output') {
55+
return this.state.values;
56+
}
57+
const mapping = this.state.mapping;
58+
return service
59+
.readArrayAt(this.state.slot)
60+
.map(fields => mapping.deserialization!.fn([FieldReader.asReader(fields)]));
61+
}
62+
}
63+
64+
type EphemeralArrayState<T> =
65+
| { kind: 'output'; service: EphemeralArrayService; values: T[]; cachedSlot?: Fr }
66+
| { kind: 'input'; slot: Fr; mapping: TypeMapping<T> };

yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class EventValidationRequest {
1818
public txHash: TxHash,
1919
) {}
2020

21-
static fromFields(fields: Fr[]): EventValidationRequest {
21+
static fromFields(fields: Fr[] | FieldReader): EventValidationRequest {
2222
const reader = FieldReader.asReader(fields);
2323

2424
const contractAddress = AztecAddress.fromField(reader.readField());
@@ -36,7 +36,7 @@ export class EventValidationRequest {
3636

3737
if (reader.remainingFields() !== 0) {
3838
throw new Error(
39-
`Error converting array of fields to EventValidationRequest: expected ${reader.cursor} fields but received ${fields.length} (maxEventSerializedLen=${maxEventSerializedLen}).`,
39+
`Error converting array of fields to EventValidationRequest: expected ${reader.cursor} fields but received ${reader.cursor + reader.remainingFields()} (maxEventSerializedLen=${maxEventSerializedLen}).`,
4040
);
4141
}
4242

yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class NoteValidationRequest {
2020
public txHash: TxHash,
2121
) {}
2222

23-
static fromFields(fields: Fr[]): NoteValidationRequest {
23+
static fromFields(fields: Fr[] | FieldReader): NoteValidationRequest {
2424
const reader = FieldReader.asReader(fields);
2525

2626
const contractAddress = AztecAddress.fromField(reader.readField());
@@ -40,7 +40,7 @@ export class NoteValidationRequest {
4040

4141
if (reader.remainingFields() !== 0) {
4242
throw new Error(
43-
`Error converting array of fields to NoteValidationRequest: expected ${reader.cursor} fields but received ${fields.length} (maxNotePackedLen=${maxNotePackedLen}).`,
43+
`Error converting array of fields to NoteValidationRequest: expected ${reader.cursor} fields but received ${reader.cursor + reader.remainingFields()} (maxNotePackedLen=${maxNotePackedLen}).`,
4444
);
4545
}
4646

yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import type { ARCHIVE_HEIGHT, L1_TO_L2_MSG_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
22
import type { BlockNumber } from '@aztec/foundation/branded-types';
33
import type { Fr } from '@aztec/foundation/curves/bn254';
4+
import type { Point } from '@aztec/foundation/curves/grumpkin';
45
import type { MembershipWitness } from '@aztec/foundation/trees';
56
import type { FunctionSelector, NoteSelector } from '@aztec/stdlib/abi';
67
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
78
import type { BlockHash } from '@aztec/stdlib/block';
89
import type { ContractInstance, PartialAddress } from '@aztec/stdlib/contract';
910
import type { KeyValidationRequest } from '@aztec/stdlib/kernel';
1011
import type { PublicKeys } from '@aztec/stdlib/keys';
11-
import type { ContractClassLog, Tag } from '@aztec/stdlib/logs';
12+
import type { ContractClassLog, MessageContext, PendingTaggedLog, Tag } from '@aztec/stdlib/logs';
1213
import type { Note, NoteStatus } from '@aztec/stdlib/note';
1314
import type { NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
1415
import type { BlockHeader, TxEffect, TxHash } from '@aztec/stdlib/tx';
1516

1617
import type { BoundedVec } from '../noir-structs/bounded_vec.js';
18+
import type { EphemeralArray } from '../noir-structs/ephemeral_array.js';
19+
import type { EventValidationRequest } from '../noir-structs/event_validation_request.js';
20+
import type { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js';
21+
import type { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js';
22+
import type { NoteValidationRequest } from '../noir-structs/note_validation_request.js';
1723
import type { Option } from '../noir-structs/option.js';
1824
import type { UtilityContext } from '../noir-structs/utility_context.js';
1925
import type { MessageLoadOracleInputs } from './message_load_oracle_inputs.js';
@@ -116,14 +122,16 @@ export interface IUtilityExecutionOracle {
116122
startStorageSlot: Fr,
117123
numberOfElements: number,
118124
): Promise<Fr[]>;
119-
getPendingTaggedLogs(scope: AztecAddress): Promise<Fr>;
125+
getPendingTaggedLogs(scope: AztecAddress): Promise<EphemeralArray<PendingTaggedLog>>;
120126
validateAndStoreEnqueuedNotesAndEvents(
121-
noteValidationRequestsArrayBaseSlot: Fr,
122-
eventValidationRequestsArrayBaseSlot: Fr,
127+
noteValidationRequests: EphemeralArray<NoteValidationRequest>,
128+
eventValidationRequests: EphemeralArray<EventValidationRequest>,
123129
scope: AztecAddress,
124130
): Promise<void>;
125-
getLogsByTag(requestArrayBaseSlot: Fr): Promise<Fr>;
126-
getMessageContextsByTxHash(requestArrayBaseSlot: Fr): Promise<Fr>;
131+
getLogsByTag(
132+
requests: EphemeralArray<LogRetrievalRequest>,
133+
): Promise<EphemeralArray<EphemeralArray<LogRetrievalResponse>>>;
134+
getMessageContextsByTxHash(requests: EphemeralArray<Fr>): Promise<EphemeralArray<Option<MessageContext>>>;
127135
getTxEffect(txHash: TxHash): Promise<Option<TxEffect>>;
128136
setCapsule(contractAddress: AztecAddress, key: Fr, capsule: Fr[], scope: AztecAddress): void;
129137
getCapsule(contractAddress: AztecAddress, key: Fr, tSize: number, scope: AztecAddress): Promise<Option<Fr[]>>;
@@ -136,7 +144,11 @@ export interface IUtilityExecutionOracle {
136144
scope: AztecAddress,
137145
): Promise<void>;
138146
decryptAes128(ciphertext: BoundedVec<number>, iv: Buffer, symKey: Buffer): Promise<Option<BoundedVec<number>>>;
139-
getSharedSecrets(address: AztecAddress, ephPksSlot: Fr, contractAddress: AztecAddress): Promise<Fr>;
147+
getSharedSecrets(
148+
address: AztecAddress,
149+
ephPks: EphemeralArray<Point>,
150+
contractAddress: AztecAddress,
151+
): Promise<EphemeralArray<Fr>>;
140152
setContractSyncCacheInvalid(contractAddress: AztecAddress, scopes: BoundedVec<AztecAddress>): void;
141153
emitOffchainEffect(data: Fr[]): Promise<void>;
142154
callUtilityFunction(

yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -514,10 +514,10 @@ export class Oracle {
514514
return callHandler({
515515
oracle: 'aztec_utl_validateAndStoreEnqueuedNotesAndEvents',
516516
inputs,
517-
handler: ([noteValidationRequestsArrayBaseSlot, eventValidationRequestsArrayBaseSlot, scope]) =>
517+
handler: ([noteValidationRequests, eventValidationRequests, scope]) =>
518518
this.handlerAsUtility().validateAndStoreEnqueuedNotesAndEvents(
519-
noteValidationRequestsArrayBaseSlot,
520-
eventValidationRequestsArrayBaseSlot,
519+
noteValidationRequests,
520+
eventValidationRequests,
521521
scope,
522522
),
523523
});
@@ -528,7 +528,7 @@ export class Oracle {
528528
return callHandler({
529529
oracle: 'aztec_utl_getLogsByTag',
530530
inputs,
531-
handler: ([requestArrayBaseSlot]) => this.handlerAsUtility().getLogsByTag(requestArrayBaseSlot),
531+
handler: ([requests]) => this.handlerAsUtility().getLogsByTag(requests),
532532
});
533533
}
534534

@@ -537,7 +537,7 @@ export class Oracle {
537537
return callHandler({
538538
oracle: 'aztec_utl_getMessageContextsByTxHash',
539539
inputs,
540-
handler: ([requestArrayBaseSlot]) => this.handlerAsUtility().getMessageContextsByTxHash(requestArrayBaseSlot),
540+
handler: ([requests]) => this.handlerAsUtility().getMessageContextsByTxHash(requests),
541541
});
542542
}
543543

@@ -675,8 +675,8 @@ export class Oracle {
675675
return callHandler({
676676
oracle: 'aztec_utl_getSharedSecrets',
677677
inputs,
678-
handler: ([address, ephPksSlot, contractAddress]) =>
679-
this.handlerAsUtility().getSharedSecrets(address, ephPksSlot, contractAddress),
678+
handler: ([address, ephPks, contractAddress]) =>
679+
this.handlerAsUtility().getSharedSecrets(address, ephPks, contractAddress),
680680
});
681681
}
682682

yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { BlockNumber } from '@aztec/foundation/branded-types';
1616
import { padArrayEnd } from '@aztec/foundation/collection';
1717
import { Fr } from '@aztec/foundation/curves/bn254';
18+
import { Point } from '@aztec/foundation/curves/grumpkin';
1819
import { FieldReader } from '@aztec/foundation/serialize';
1920
import { MembershipWitness } from '@aztec/foundation/trees';
2021
import { type ACVMField, fromUintArray, toACVMField } from '@aztec/simulator/client';
@@ -25,18 +26,31 @@ import { BlockHash } from '@aztec/stdlib/block';
2526
import type { ContractInstance, PartialAddress } from '@aztec/stdlib/contract';
2627
import { KeyValidationRequest } from '@aztec/stdlib/kernel';
2728
import type { PublicKeys } from '@aztec/stdlib/keys';
28-
import { ContractClassLog, ContractClassLogFields, FlatPublicLogs, PrivateLog, Tag } from '@aztec/stdlib/logs';
29+
import {
30+
ContractClassLog,
31+
ContractClassLogFields,
32+
FlatPublicLogs,
33+
MessageContext,
34+
PendingTaggedLog,
35+
PrivateLog,
36+
Tag,
37+
} from '@aztec/stdlib/logs';
2938
import { NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
3039
import { BlockHeader, TxEffect, TxHash } from '@aztec/stdlib/tx';
3140

3241
import { BoundedVec } from '../noir-structs/bounded_vec.js';
42+
import { EphemeralArray } from '../noir-structs/ephemeral_array.js';
43+
import { EventValidationRequest } from '../noir-structs/event_validation_request.js';
44+
import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js';
45+
import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js';
46+
import { NoteValidationRequest } from '../noir-structs/note_validation_request.js';
3347
import { Option } from '../noir-structs/option.js';
3448
import { UtilityContext } from '../noir-structs/utility_context.js';
3549
import type { NoteData } from './interfaces.js';
3650
import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js';
3751
import { packAsHintedNote } from './note_packing_utils.js';
3852

39-
const FIELD: TypeMapping<Fr> = {
53+
export const FIELD: TypeMapping<Fr> = {
4054
serialization: { fn: v => [v] },
4155
deserialization: { fn: ([reader]) => reader.readField(), slots: 1 },
4256
};
@@ -254,6 +268,48 @@ const NOTE: TypeMapping<NoteData> = {
254268
},
255269
};
256270

271+
export const POINT: TypeMapping<Point> = {
272+
serialization: { fn: p => [p.toFields()] },
273+
deserialization: {
274+
fn: ([reader]) => Point.fromFields([reader.readField(), reader.readField()]),
275+
slots: 1,
276+
},
277+
};
278+
279+
export const PENDING_TAGGED_LOG: TypeMapping<PendingTaggedLog> = {
280+
serialization: { fn: log => [log.toFields()] },
281+
};
282+
283+
export const NOTE_VALIDATION_REQUEST: TypeMapping<NoteValidationRequest> = {
284+
deserialization: {
285+
fn: ([reader]) => NoteValidationRequest.fromFields(reader),
286+
slots: 1,
287+
},
288+
};
289+
290+
export const EVENT_VALIDATION_REQUEST: TypeMapping<EventValidationRequest> = {
291+
deserialization: {
292+
fn: ([reader]) => EventValidationRequest.fromFields(reader),
293+
slots: 1,
294+
},
295+
};
296+
297+
export const LOG_RETRIEVAL_REQUEST: TypeMapping<LogRetrievalRequest> = {
298+
serialization: { fn: req => [req.toFields()] },
299+
deserialization: {
300+
fn: ([reader]) => LogRetrievalRequest.fromFields(reader),
301+
slots: 1,
302+
},
303+
};
304+
305+
export const LOG_RETRIEVAL_RESPONSE: TypeMapping<LogRetrievalResponse> = {
306+
serialization: { fn: resp => [resp.toFields()] },
307+
};
308+
309+
export const MESSAGE_CONTEXT: TypeMapping<MessageContext> = {
310+
serialization: { fn: mc => [mc.toFields()] },
311+
};
312+
257313
const ORACLE_REGISTRY = {
258314
aztec_utl_assertCompatibleOracleVersion: makeEntry({
259315
params: [
@@ -389,25 +445,25 @@ const ORACLE_REGISTRY = {
389445

390446
aztec_utl_getPendingTaggedLogs: makeEntry({
391447
params: [{ name: 'scope', type: AZTEC_ADDRESS }],
392-
returnType: FIELD,
448+
returnType: EPHEMERAL_ARRAY(PENDING_TAGGED_LOG),
393449
}),
394450

395451
aztec_utl_validateAndStoreEnqueuedNotesAndEvents: makeEntry({
396452
params: [
397-
{ name: 'noteValidationRequestsArrayBaseSlot', type: FIELD },
398-
{ name: 'eventValidationRequestsArrayBaseSlot', type: FIELD },
453+
{ name: 'noteValidationRequests', type: EPHEMERAL_ARRAY(NOTE_VALIDATION_REQUEST) },
454+
{ name: 'eventValidationRequests', type: EPHEMERAL_ARRAY(EVENT_VALIDATION_REQUEST) },
399455
{ name: 'scope', type: AZTEC_ADDRESS },
400456
],
401457
}),
402458

403459
aztec_utl_getLogsByTag: makeEntry({
404-
params: [{ name: 'requestArrayBaseSlot', type: FIELD }],
405-
returnType: FIELD,
460+
params: [{ name: 'requests', type: EPHEMERAL_ARRAY(LOG_RETRIEVAL_REQUEST) }],
461+
returnType: EPHEMERAL_ARRAY(EPHEMERAL_ARRAY(LOG_RETRIEVAL_RESPONSE)),
406462
}),
407463

408464
aztec_utl_getMessageContextsByTxHash: makeEntry({
409-
params: [{ name: 'requestArrayBaseSlot', type: FIELD }],
410-
returnType: FIELD,
465+
params: [{ name: 'requests', type: EPHEMERAL_ARRAY(FIELD) }],
466+
returnType: EPHEMERAL_ARRAY(OPTION(MESSAGE_CONTEXT)),
411467
}),
412468

413469
aztec_utl_getTxEffect: makeEntry({
@@ -464,10 +520,10 @@ const ORACLE_REGISTRY = {
464520
aztec_utl_getSharedSecrets: makeEntry({
465521
params: [
466522
{ name: 'address', type: AZTEC_ADDRESS },
467-
{ name: 'ephPksSlot', type: FIELD },
523+
{ name: 'ephPks', type: EPHEMERAL_ARRAY(POINT) },
468524
{ name: 'contractAddress', type: AZTEC_ADDRESS },
469525
],
470-
returnType: FIELD,
526+
returnType: EPHEMERAL_ARRAY(FIELD),
471527
}),
472528

473529
aztec_utl_setContractSyncCacheInvalid: makeEntry({
@@ -820,7 +876,7 @@ function BOUNDED_VEC<T>(element: TypeMapping<T>): TypeMapping<BoundedVec<T>> {
820876
* slot 1: Fr(0) // zero-filled using shape
821877
* ```
822878
*/
823-
function OPTION<T>(inner: TypeMapping<T>): TypeMapping<Option<T>> {
879+
export function OPTION<T>(inner: TypeMapping<T>): TypeMapping<Option<T>> {
824880
return {
825881
serialization: inner.serialization
826882
? {
@@ -870,6 +926,17 @@ function BUFFER(bitSize: number): TypeMapping<Buffer> {
870926
};
871927
}
872928

929+
export function EPHEMERAL_ARRAY<T>(element: TypeMapping<T>): TypeMapping<EphemeralArray<T>> {
930+
return {
931+
serialization: element.serialization
932+
? { fn: ea => [ea.materializeSlot(v => element.serialization!.fn(v).flat() as Fr[])] }
933+
: undefined,
934+
deserialization: element.deserialization
935+
? { fn: ([reader]) => EphemeralArray.fromSlot(reader.readField(), element), slots: 1 }
936+
: undefined,
937+
};
938+
}
939+
873940
/** A named oracle parameter with its TypeMapping. */
874941
interface RegistryParam<TName extends string = string, T = any> {
875942
name: TName;

0 commit comments

Comments
 (0)