From d9e406f819272633b0bfeedb20d5c4187e076ea7 Mon Sep 17 00:00:00 2001 From: Nico Chamo Date: Fri, 15 May 2026 13:16:23 -0300 Subject: [PATCH] feat(txe): add oracle version check to bootstrap --- yarn-project/bootstrap.sh | 4 +- yarn-project/pxe/package.json | 3 +- .../pxe/src/bin/check_oracle_version.ts | 66 +++++++++++-------- yarn-project/pxe/src/bin/index.ts | 1 + yarn-project/txe/package.json | 3 +- .../txe/src/bin/check_txe_oracle_version.ts | 37 +++++++++++ yarn-project/txe/src/txe_oracle_version.ts | 8 +++ 7 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 yarn-project/pxe/src/bin/index.ts create mode 100644 yarn-project/txe/src/bin/check_txe_oracle_version.ts diff --git a/yarn-project/bootstrap.sh b/yarn-project/bootstrap.sh index d263e662de9c..140dd2c6ca78 100755 --- a/yarn-project/bootstrap.sh +++ b/yarn-project/bootstrap.sh @@ -143,9 +143,11 @@ function compile_all { get_projects | compile_project - # Run oracle version check for pxe after compilation + # Run oracle version checks after compilation cd pxe && yarn check_oracle_version cd .. + cd txe && yarn check_txe_oracle_version + cd .. cmds=('format --check' 'yarn tsgo -b --emitDeclarationOnly') if [ "${CI:-0}" -eq 1 ]; then diff --git a/yarn-project/pxe/package.json b/yarn-project/pxe/package.json index 0cfaf010e6e8..35908adc42f2 100644 --- a/yarn-project/pxe/package.json +++ b/yarn-project/pxe/package.json @@ -16,7 +16,8 @@ "./client/lazy": "./dest/entrypoints/client/lazy/index.js", "./client/bundle": "./dest/entrypoints/client/bundle/index.js", "./simulator": "./dest/contract_function_simulator/index.js", - "./config": "./dest/config/index.js" + "./config": "./dest/config/index.js", + "./bin": "./dest/bin/index.js" }, "bin": "./dest/bin/index.js", "scripts": { diff --git a/yarn-project/pxe/src/bin/check_oracle_version.ts b/yarn-project/pxe/src/bin/check_oracle_version.ts index 1393d7df640e..cf45050592cf 100644 --- a/yarn-project/pxe/src/bin/check_oracle_version.ts +++ b/yarn-project/pxe/src/bin/check_oracle_version.ts @@ -16,27 +16,8 @@ import { ORACLE_INTERFACE_HASH } from '../oracle_version.js'; * changed and the oracle version needs to be bumped: * - If the change is backward-breaking (e.g. removing/renaming an oracle), bump ORACLE_VERSION_MAJOR. * - If the change is an oracle addition (non-breaking), bump ORACLE_VERSION_MINOR. - * - * TODO(F-667): The following only takes into consideration changes to the oracles defined in Oracle.ts and omits TXE - * oracles. Ensure this checks TXE oracles as well. This hasn't been implemented yet since we don't have a clean TXE - * oracle interface like we do in PXE (i.e., there is no single Oracle class that contains only the oracles). */ function assertOracleInterfaceMatches(): void { - const oracleInterfaceSignature = getOracleInterfaceSignature(); - - // We use keccak256 here just because we already have it in the dependencies. - const oracleInterfaceHash = keccak256String(oracleInterfaceSignature); - if (oracleInterfaceHash !== ORACLE_INTERFACE_HASH) { - throw new Error( - `The Oracle interface has changed. Update ORACLE_INTERFACE_HASH to ${oracleInterfaceHash} in pxe/src/oracle_version.ts and bump the oracle version (ORACLE_VERSION_MAJOR for breaking changes, ORACLE_VERSION_MINOR for oracle additions).`, - ); - } -} - -/** - * Constructs a signature of the Oracle interface while ignoring methods that are not foreign call handlers. - */ -function getOracleInterfaceSignature(): string { const excludedProps = [ 'handler', 'constructor', @@ -44,7 +25,7 @@ function getOracleInterfaceSignature(): string { 'handlerAsMisc', 'handlerAsUtility', 'handlerAsPrivate', - ] as const; + ]; // Get the path to Oracle.ts source file // The script runs from dest/bin/ after compilation, so we need to go up to the package root @@ -54,23 +35,52 @@ function getOracleInterfaceSignature(): string { const packageRoot = dirname(dirname(currentDir)); // Go up from bin/ to pxe/ const oracleSourcePath = join(packageRoot, 'src/contract_function_simulator/oracle/oracle.ts'); + const oracleInterfaceSignature = getOracleInterfaceSignature(oracleSourcePath, ['Oracle'], excludedProps); + + // We use keccak256 here just because we already have it in the dependencies. + const oracleInterfaceHash = keccak256String(oracleInterfaceSignature); + if (oracleInterfaceHash !== ORACLE_INTERFACE_HASH) { + throw new Error( + `The Oracle interface has changed. Update ORACLE_INTERFACE_HASH to ${oracleInterfaceHash} in pxe/src/oracle_version.ts and bump the oracle version (ORACLE_VERSION_MAJOR for breaking changes, ORACLE_VERSION_MINOR for oracle additions).`, + ); + } +} + +/** + * Extracts method signatures from TypeScript classes or interfaces and returns a deterministic string representation. + * + * This is used to detect when an oracle interface changes so that the oracle version can be bumped. It works with both + * class declarations (e.g. PXE's `Oracle` class) and interface declarations (e.g. TXE's `IAvmExecutionOracle`). + * + * @param sourcePath - Absolute path to the TypeScript source file to parse. + * @param targets - Names of classes or interfaces to extract methods from. + * @param excludedMembers - Method names to skip (e.g. non-oracle helpers like `constructor`). + */ +export function getOracleInterfaceSignature(sourcePath: string, targets: string[], excludedMembers: string[]): string { // Read and parse the TypeScript source file - const sourceCode = readFileSync(oracleSourcePath, 'utf-8'); - const sourceFile = ts.createSourceFile('oracle.ts', sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const sourceCode = readFileSync(sourcePath, 'utf-8'); + const sourceFile = ts.createSourceFile(sourcePath, sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); - // Extract method signatures from the Oracle class + // Extract method signatures from the target classes/interfaces const methodSignatures: string[] = []; function visit(node: ts.Node) { - // Look for class declaration named "Oracle" - if (ts.isClassDeclaration(node) && node.name?.text === 'Oracle') { - // Visit all members of the class + // Look for class or interface declarations matching the target names + const isTarget = + (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && targets.includes(node.name?.text ?? ''); + + if (isTarget) { + // Visit all members of the class/interface node.members.forEach(member => { - if (ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name)) { + if ( + (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) && + member.name && + ts.isIdentifier(member.name) + ) { const methodName = member.name.text; // Skip excluded methods - if (excludedProps.includes(methodName as (typeof excludedProps)[number])) { + if (excludedMembers.includes(methodName)) { return; } diff --git a/yarn-project/pxe/src/bin/index.ts b/yarn-project/pxe/src/bin/index.ts new file mode 100644 index 000000000000..bda0bdc9d99b --- /dev/null +++ b/yarn-project/pxe/src/bin/index.ts @@ -0,0 +1 @@ +export { getOracleInterfaceSignature } from './check_oracle_version.js'; diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index 9f39b19e10a7..604e26f20fb5 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -17,7 +17,8 @@ "clean": "rm -rf ./dest .tsbuildinfo", "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}", "dev": "LOG_LEVEL=\"debug; trace: simulator:state_manager; info: json-rpc:proxy\" node ./dest/bin/index.js", - "start": "node --no-warnings ./dest/bin/index.js" + "start": "node --no-warnings ./dest/bin/index.js", + "check_txe_oracle_version": "node ./dest/bin/check_txe_oracle_version.js" }, "inherits": [ "../package.common.json" diff --git a/yarn-project/txe/src/bin/check_txe_oracle_version.ts b/yarn-project/txe/src/bin/check_txe_oracle_version.ts new file mode 100644 index 000000000000..889b48f912ab --- /dev/null +++ b/yarn-project/txe/src/bin/check_txe_oracle_version.ts @@ -0,0 +1,37 @@ +import { keccak256String } from '@aztec/foundation/crypto/keccak'; +import { getOracleInterfaceSignature } from '@aztec/pxe/bin'; + +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +import { TXE_ORACLE_INTERFACE_HASH } from '../txe_oracle_version.js'; + +/** + * Verifies that the TXE oracle interfaces match the expected interface hash. + * + * The TXE oracle interfaces need to be versioned to ensure compatibility between Aztec.nr tests and TXE. This function + * computes a hash of the TXE oracle interfaces and compares it against a known hash. If they don't match, it means an + * interface has changed and the TXE oracle version needs to be bumped: + * - If the change is backward-breaking (e.g. removing/renaming an oracle), bump TXE_ORACLE_VERSION_MAJOR. + * - If the change is an oracle addition (non-breaking), bump TXE_ORACLE_VERSION_MINOR. + */ +function assertTxeOracleInterfaceMatches(): void { + const currentDir = dirname(fileURLToPath(import.meta.url)); + const packageRoot = dirname(dirname(currentDir)); + const interfacesSourcePath = join(packageRoot, 'src/oracle/interfaces.ts'); + + const targets = ['IAvmExecutionOracle', 'ITxeExecutionOracle']; + // Not an oracle foreign call handler (see TODO(F-335) in interfaces.ts). + const excludedMembers = ['syncContractNonOracleMethod']; + + const txeOracleInterfaceSignature = getOracleInterfaceSignature(interfacesSourcePath, targets, excludedMembers); + + const txeOracleInterfaceHash = keccak256String(txeOracleInterfaceSignature); + if (txeOracleInterfaceHash !== TXE_ORACLE_INTERFACE_HASH) { + throw new Error( + `The TXE oracle interface has changed. Update TXE_ORACLE_INTERFACE_HASH to ${txeOracleInterfaceHash} in txe/src/txe_oracle_version.ts and bump the TXE oracle version (TXE_ORACLE_VERSION_MAJOR for breaking changes, TXE_ORACLE_VERSION_MINOR for oracle additions).`, + ); + } +} + +assertTxeOracleInterfaceMatches(); diff --git a/yarn-project/txe/src/txe_oracle_version.ts b/yarn-project/txe/src/txe_oracle_version.ts index ae8712abb4c0..45d43f2be6a1 100644 --- a/yarn-project/txe/src/txe_oracle_version.ts +++ b/yarn-project/txe/src/txe_oracle_version.ts @@ -7,3 +7,11 @@ */ export const TXE_ORACLE_VERSION_MAJOR = 1; export const TXE_ORACLE_VERSION_MINOR = 0; + +/** + * This hash is computed from the TXE oracle interfaces (IAvmExecutionOracle and ITxeExecutionOracle) and is used to + * detect when those interfaces change. When it does, bump: + * - TXE_ORACLE_VERSION_MAJOR (and reset MINOR to 0) for breaking changes, or + * - TXE_ORACLE_VERSION_MINOR for additive changes (new oracle method added). + */ +export const TXE_ORACLE_INTERFACE_HASH = '6ab5922a46d54edc4e1f10d6bd734bd35d8e6dff0dd2660091d25af55563f68a';