Skip to content
Open
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
4 changes: 3 additions & 1 deletion yarn-project/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/pxe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
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.

Honestly, I'm not sure if it's best to generalize and export getOracleInterfaceSignature to re-use from TXE, or copy-paste the logic

What do you think? I went this way, but I can easily go the other way if you think that would be best

},
"bin": "./dest/bin/index.js",
"scripts": {
Expand Down
66 changes: 38 additions & 28 deletions yarn-project/pxe/src/bin/check_oracle_version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,16 @@ 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',
'toACIRCallback',
'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
Expand All @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions yarn-project/pxe/src/bin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getOracleInterfaceSignature } from './check_oracle_version.js';
3 changes: 2 additions & 1 deletion yarn-project/txe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
37 changes: 37 additions & 0 deletions yarn-project/txe/src/bin/check_txe_oracle_version.ts
Original file line number Diff line number Diff line change
@@ -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();
8 changes: 8 additions & 0 deletions yarn-project/txe/src/txe_oracle_version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Loading