Skip to content

refactor(pxe): compute oracle interface hash from wire-structural mapping labels - #24752

Merged
nchamo merged 5 commits into
merge-train/fairies-v5from
nchamo/oracle-type-mapping-kinds
Jul 20, 2026
Merged

refactor(pxe): compute oracle interface hash from wire-structural mapping labels#24752
nchamo merged 5 commits into
merge-train/fairies-v5from
nchamo/oracle-type-mapping-kinds

Conversation

@nchamo

@nchamo nchamo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

The oracle interface hash — the major.minor guard that keeps PXE and Aztec.nr in sync — was computed by parsing the TypeScript source of ORACLE_REGISTRY with a ~350-line TS-compiler AST walker, reading parameter names and type identifiers straight out of the source text. That was indirect and brittle: it tracked how the registry was written rather than the wire types the mappings actually produce, and no mapping ever named the Noir type it stands for.

Our fix

First, some cleanup:

  • Introduced three small builders for declaring mappings:
    • LEAF — a mapping the hash treats as atomic (its label is just its kind): scalars and hand-serialized types.
    • SCALAR — a single-field LEAF that omits the shape.
    • ALIAS — a TS-side alias over a base mapping: identical wire, richer TS value (e.g. TX_HASH over Field).
  • Started composing STRUCT over field mappings instead of hand-serializing, wherever the type is a plain record (ORIGIN_BLOCK, FACT, FACT_COLLECTION, RESOLVED_TX, PROVIDED_SECRET, …).
  • Fixed a few type names: BYTEU8, split the catch-all BIGINT into honest U64 / U128 / LEAF_INDEX, and mapped RETRACTABLE_FACT_ORIGIN.blockState as a validated origin-block-state scalar instead of a U32 placeholder.

Then, two fields on every mapping:

  • kind — the discriminant the combinators already used ('array', 'option', …); now required on all mappings, and for a scalar it also names the Noir primitive.
  • label — the canonical Noir type string the hash is computed from. Equal to kind for a leaf, structural for a composite (array(field,4), {field,u32}). Labels are wire-structural: a struct's label drops its field names and splices nested struct labels into the parent, so anything the wire tolerates (a field rename, a struct-in-struct nesting refactor) never moves the hash, and a genuine layout change always does.

The hash is now a keccak of the registry walked through these labels, so it reflects the real wire contract. Both interface hashes (PXE and TXE) are re-pinned — a TS-only build-time guard, so no contract bytecode or standard-contract addresses change, and the wire stays byte-identical so the oracle versions are untouched.

Moving ResolvedTx's wire layout into the RESOLVED_TX struct mapping orphaned the class's toFields(), so ResolvedTx becomes a plain type: the class, its inline-data pin test, and the Noir resolved_tx_serialization_matches_typescript counterpart are deleted — the generated oracle tests already verify that wire end-to-end.

As a bonus, anchoring each mapping to its Noir type also feeds the auto-generated oracle serialization tests.

@nchamo nchamo self-assigned this Jul 16, 2026
@nchamo nchamo added ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure labels Jul 16, 2026
const { params, returnType } = extractRegistryEntry(property.initializer, sourceFile);
const paramSignatures = params.map(param => `${param.name}: ${param.type}`);
return `${oracleName}(${paramSignatures.join(', ')}): ${returnType}`;
export function getOracleRegistrySignature(registry: Record<string, OracleRegistryEntry>): string {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is now so simple

{ name: 'ciphertext', type: BOUNDED_VEC(BYTE) },
{ name: 'iv', type: BUFFER(8, 16) },
{ name: 'symKey', type: BUFFER(8, 16) },
{ name: 'ciphertext', type: BOUNDED_VEC(U8) },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed BYTE to U8 to be consistent with Noir

ciphertext: BoundedVec<number>,
iv: Buffer,
symKey: Buffer,
iv: number[],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is actually more consistent with Noir. We can create the Buffer here

const GAS_FEES: TypeMapping<GasFees> = STRUCT<GasFees>([
{ name: 'feePerDaGas', type: BIGINT },
{ name: 'feePerL2Gas', type: BIGINT },
{ name: 'feePerDaGas', type: U128 },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This now reflects what Noir actually says

serialization: { fn: resolved => [resolved.toFields()] },
shape: [{ len: MAX_NOTE_HASHES_PER_TX + 5 }],
};
export const RESOLVED_TX: TypeMapping<ResolvedTx> = STRUCT([

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

RESOLVED_TX (and a few more below) are now STRUCTs. We are very close to having all types reflect their Noir counterpart 100%

function SIBLING_PATH<N extends number>(height: N): TypeMapping<SiblingPath<N>> {
return LEAF({
// On the wire (and in Noir) a sibling path is a plain `[Field; height]`, so it shares the fixed-array kind.
kind: `array(field,${height})`,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is wrong. SIBLING_PATH should basically be an array, but we need to make some bigger changes to support that. I'll do that as a follow up, didn't want to make this PR too big

Comment thread yarn-project/pxe/src/oracle_version.ts Outdated
///
/// These constants must be kept in sync between this file and `noir-projects/aztec-nr/aztec/src/oracle/version.nr`.
export const ORACLE_INTERFACE_HASH = 'b302a8e65d37043c0a0dc08a39895603122dce334843f0442462edb3f56e32e2';
export const ORACLE_INTERFACE_HASH = 'b1bcd3a71cb7142001d914f63d43be25d8508aacb99fe89c611a4b6e410d423f';

@nchamo nchamo Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No version bump needed since we didn't actually change the oracle, we just changed how we calculate the hash

@nchamo
nchamo marked this pull request as ready for review July 16, 2026 15:38
@nchamo
nchamo requested a review from nventuro as a code owner July 16, 2026 15:38
@nchamo
nchamo requested a review from vezenovm July 16, 2026 15:38
@nchamo nchamo changed the title refactor(pxe): compute oracle interface hash from mapping labels refactor(pxe): compute oracle interface hash from wire-structural mapping labels Jul 20, 2026

@vezenovm vezenovm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Awesome!

#[oracle(aztec_utl_getResolvedTxs)]
unconstrained fn get_resolved_txs_oracle(requests: EphemeralArray<Field>) -> EphemeralArray<Option<ResolvedTx>> {}

mod test {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎉

Comment thread yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts Outdated
#toTxEffectData(txEffect: TxEffect): TxEffectData {
return {
...txEffect,
revertCode: txEffect.revertCode.getCode(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

were we missing this before and now we starting failing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When we cleaned things up a little bit to make the mapping closer to Noir, we made revert code a number instead of the previous ts type it had. That's why we have to get the code here

@nchamo
nchamo enabled auto-merge (squash) July 20, 2026 16:02
@nchamo
nchamo merged commit d2db074 into merge-train/fairies-v5 Jul 20, 2026
12 checks passed
@nchamo
nchamo deleted the nchamo/oracle-type-mapping-kinds branch July 20, 2026 16:46
@PhilWindle PhilWindle added port-to-next Forward-port this merged PR into next and removed port-to-next Forward-port this merged PR into next labels Jul 21, 2026
PhilWindle pushed a commit that referenced this pull request Jul 21, 2026
nchamo added a commit that referenced this pull request Jul 23, 2026
rangozd pushed a commit to rangozd/aztec-packages that referenced this pull request Aug 5, 2026
…ecProtocol#24932)

Forward-ports the **pxe / client / txe** slice of the v5-next → next
backlog (work merged to `v5-next` after the ~2026-07-08 cut that
reshaped `next`).

## Applied (clean cherry-picks, chronological)
- fix: tweak depositToAztec gas config (AztecProtocol#24607)
- refactor: cache Aztec node reads per execution (AztecProtocol#24630)
- fix: prevent access to secrets not in scope (AztecProtocol#24616)
- docs: fee readme improvements (AztecProtocol#24666)
- fix(pxe): widen tracked sender tagging ranges with onchain discovery
evidence (AztecProtocol#24655)
- fix: tagging secrets not being scoped by sender (AztecProtocol#24772)

## Conflict resolutions (cherry-picked with `-x`, resolved against
reshaped `next`)
- fix(txe): align tagging strategy oracle with PXE (AztecProtocol#24561) — TXE oracle
version bumped to 4.0 (breaking rename `setTaggingSecretStrategy` ->
`setTaggingSecretStrategies`; `next` was at 3.0 with its own hash),
interface hash recomputed on the merged registry. Migration note
dropped: already on `next` under 5.0.0.
- feat(pxe)!: Add AppTaggingSecret kinds to keys in tagging stores
(AztecProtocol#24604) — `PXE_DATA_SCHEMA_VERSION` 12 -> 13 applies cleanly on `next`.
Migration note dropped: already on `next` under 5.0.0.
- feat: add batch is block in archive oracle (AztecProtocol#24634) — applied cleanly
on top of AztecProtocol#24561; contract oracle version 30.6 -> 30.7, v5 interface
hash matched the merged registry.
- feat: getTxEffects oracle (AztecProtocol#24636) — applied cleanly.
- ~~feat: preserve stores on schema version or rollup address change
(AztecProtocol#24631)~~ — ported separately via AztecProtocol#24947 together with the rest of the
sqlite/OPFS line
- feat(txe): add option to authorize all utility call targets (AztecProtocol#24662) —
additive on our 4.0 -> TXE oracle version 4.1 (was 3.0 -> 3.1 on v5),
hash recomputed.
- refactor(pxe): compute oracle interface hash from wire-structural
mapping labels (AztecProtocol#24752) — TXE hash recomputed under the new
wire-structural scheme; PXE hash from the pick matched.

Verified locally: full `yarn build`, `check_oracle_version` +
`check_txe_oracle_version`, TXE unit suite (21), pxe
tagging/type-mapping/utility-oracle suites, and 29 targeted aztec-nr +
onchain_delivery_test_contract TXE tests — all green.

Part of the manual v5-next → next backlog sweep.

## Added after review
- AztecProtocol#24627 fix(aztec.js): give waitForNode a bounded default timeout —
missed by the initial sweep (merge-commit wrapper + non-`(#N)` leaf);
genuinely absent from `next`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants