Skip to content

Commit 76b6290

Browse files
authored
feat(txe): add oracle versioning for test environment (#23285)
1 parent 455fe06 commit 76b6290

10 files changed

Lines changed: 133 additions & 13 deletions

File tree

docs/docs-developers/docs/aztec-nr/testing_contracts.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ aztec test
4141
2. Run `aztec test`
4242

4343
:::warning
44-
Always use `aztec test` instead of `nargo test`. The `TestEnvironment` requires the TXE (Test eXecution Environment) oracle resolver.
44+
Always use `aztec test` instead of `nargo test`. The `TestEnvironment` requires the test environment oracle resolver provided by the `aztec` CLI.
4545
:::
4646

4747
## Basic test structure
@@ -348,3 +348,25 @@ unconstrained fn test_missing_authwit() {
348348
}
349349

350350
```
351+
352+
## Test environment oracle versioning
353+
354+
The test environment uses an oracle interface to communicate between your Noir test code and the `aztec test` CLI. This interface is versioned so that mismatches between the Aztec.nr dependency used to compile the test and the CLI version are detected automatically.
355+
356+
The version uses two components, `major.minor`, with the same compatibility rules as [PXE oracle versioning](../foundational-topics/pxe/index.md#oracle-versioning):
357+
358+
- **`major`** must match exactly. A major bump means oracles were removed or had their signatures changed, and a test environment on a different major cannot safely run the test.
359+
- **`minor`** indicates additive changes (new oracles). The test environment uses a best-effort approach: a test compiled against a higher `minor` is still allowed to run, and an error is only thrown if the test actually invokes an oracle the test environment does not know about.
360+
361+
### Resolving a version mismatch
362+
363+
If you see an error like _"Incompatible test environment version: The test was compiled with a newer version of Aztec.nr than your test environment supports"_, the test uses oracles from a newer Aztec.nr than your `aztec test` CLI supports.
364+
365+
To fix it, make sure your `aztec` CLI version and the `aztec` dependency in the test crate's `Nargo.toml` are on the same release. Note that the test crate's Aztec.nr version can differ from the contract crate's version, depending on your project configuration. For example, if your CLI is on `v4.3.0`, the test crate's `Nargo.toml` should reference the matching tag:
366+
367+
```toml
368+
[dependencies]
369+
aztec = { git="https://github.com/AztecProtocol/aztec-nr", tag="v4.3.0", directory="aztec" }
370+
```
371+
372+
If the test environment reports a version that _should_ include every oracle the test needs but an oracle is still missing, this is likely a bug rather than a version problem.

docs/netlify.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,3 +818,13 @@
818818
# PXE: cross-contract utility call denied by execution hook
819819
from = "/errors/11"
820820
to = "/developers/docs/aztec-nr/debugging#cross-contract-utility-call-denied"
821+
822+
[[redirects]]
823+
# Incompatible oracle version between test and test environment
824+
from = "/errors/12"
825+
to = "/developers/docs/aztec-nr/testing_contracts#test-environment-oracle-versioning"
826+
827+
[[redirects]]
828+
# Unexpected error: how to report issues
829+
from = "/errors/13"
830+
to = "/developers/docs/aztec-nr/debugging#reporting-issues"

noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ impl TestEnvironment {
301301
/// Creates a new `TestEnvironment`. This function should only be called once per test.
302302
pub unconstrained fn new() -> Self {
303303
assert_compatible_oracle_version();
304+
txe_oracles::assert_compatible_txe_oracle_version();
304305
Self {
305306
// Use an offset to avoid secret collision with account secrets. Without this, when
306307
// deploying multiple contracts and creating accounts, they could end up with identical

noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/misc.nr

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::{
22
oracle::version::{ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR},
3-
test::helpers::test_environment::TestEnvironment,
3+
test::helpers::{
4+
test_environment::TestEnvironment,
5+
txe_oracles::{TXE_ORACLE_VERSION_MAJOR, TXE_ORACLE_VERSION_MINOR},
6+
},
47
};
58
use std::test::OracleMock;
69

@@ -49,3 +52,16 @@ unconstrained fn oracle_version_is_checked_upon_env_creation() {
4952
assert_eq(mock.times_called(), 1);
5053
assert_eq(mock.get_last_params::<(Field, Field)>(), (ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR));
5154
}
55+
56+
#[test]
57+
unconstrained fn txe_oracle_version_is_checked_upon_env_creation() {
58+
let mock = OracleMock::mock("aztec_txe_assertCompatibleOracleVersion");
59+
60+
let _env = TestEnvironment::new();
61+
62+
assert_eq(mock.times_called(), 1);
63+
assert_eq(
64+
mock.get_last_params::<(Field, Field)>(),
65+
(TXE_ORACLE_VERSION_MAJOR, TXE_ORACLE_VERSION_MINOR),
66+
);
67+
}

noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ use crate::protocol::{
1414
traits::{Deserialize, ToField},
1515
};
1616

17+
/// The TXE oracle version constants are used to check that the oracle interface used for tests is in sync between
18+
/// TXE and Aztec.nr. This is separate from the contract oracle version in
19+
/// [`oracle::version`](crate::oracle::version), which covers oracles used during contract execution by PXE.
20+
///
21+
/// The TypeScript counterparts are in `yarn-project/txe/src/txe_oracle_version.ts`.
22+
pub global TXE_ORACLE_VERSION_MAJOR: Field = 1;
23+
pub global TXE_ORACLE_VERSION_MINOR: Field = 0;
24+
25+
/// Asserts that the TXE oracle interface version is compatible.
26+
pub unconstrained fn assert_compatible_txe_oracle_version() {
27+
assert_compatible_txe_oracle_version_oracle_call(TXE_ORACLE_VERSION_MAJOR, TXE_ORACLE_VERSION_MINOR);
28+
}
29+
30+
#[oracle(aztec_txe_assertCompatibleOracleVersion)]
31+
unconstrained fn assert_compatible_txe_oracle_version_oracle_call(major: Field, minor: Field) {}
32+
1733
/// Upper bound on the number of raw offchain effects the TXE test environment will surface
1834
/// to Noir in a single `get_last_call_offchain_effects` query.
1935
pub(crate) global MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY: u32 = 64;

yarn-project/pxe/src/bin/check_oracle_version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { ORACLE_INTERFACE_HASH } from '../oracle_version.js';
1717
* - If the change is backward-breaking (e.g. removing/renaming an oracle), bump ORACLE_VERSION_MAJOR.
1818
* - If the change is an oracle addition (non-breaking), bump ORACLE_VERSION_MINOR.
1919
*
20-
* TODO(#16581): The following only takes into consideration changes to the oracles defined in Oracle.ts and omits TXE
20+
* TODO(F-667): The following only takes into consideration changes to the oracles defined in Oracle.ts and omits TXE
2121
* oracles. Ensure this checks TXE oracles as well. This hasn't been implemented yet since we don't have a clean TXE
2222
* oracle interface like we do in PXE (i.e., there is no single Oracle class that contains only the oracles).
2323
*/

yarn-project/txe/src/rpc_translator.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address';
2121
import { BlockHash } from '@aztec/stdlib/block';
2222

2323
import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js';
24+
import { TXE_ORACLE_VERSION_MAJOR } from './txe_oracle_version.js';
2425
import type { TXESessionStateHandler } from './txe_session.js';
2526
import {
2627
type ForeignCallArray,
@@ -111,6 +112,26 @@ export class RPCTranslator {
111112
return this.oracleHandler;
112113
}
113114

115+
// eslint-disable-next-line camelcase
116+
aztec_txe_assertCompatibleOracleVersion(foreignMajor: ForeignCallSingle, foreignMinor: ForeignCallSingle) {
117+
const major = fromSingle(foreignMajor).toNumber();
118+
const minor = fromSingle(foreignMinor).toNumber();
119+
120+
if (major !== TXE_ORACLE_VERSION_MAJOR) {
121+
const hint =
122+
major > TXE_ORACLE_VERSION_MAJOR
123+
? 'The test was compiled with a newer version of Aztec.nr than your test environment supports. Upgrade your test environment to a compatible version.'
124+
: 'The test was compiled with an older version of Aztec.nr than your test environment supports. Recompile the test with a compatible version of Aztec.nr.';
125+
throw new Error(
126+
`Incompatible test environment version: ${hint} See https://docs.aztec.network/errors/12 (expected test oracle major version ${TXE_ORACLE_VERSION_MAJOR}, got ${major})`,
127+
);
128+
}
129+
130+
this.stateHandler.setTxeOracleVersion({ major, minor });
131+
132+
return toForeignCallResult([]);
133+
}
134+
114135
// TXE session state transition functions - these get handled by the state handler
115136

116137
// eslint-disable-next-line camelcase
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* The TXE oracle version constants are used to check that the oracle interface used for tests is in sync between
3+
* TXE and Aztec.nr. This is separate from the contract oracle version in `pxe/src/oracle_version.ts`, which covers
4+
* oracles used during contract execution by PXE.
5+
*
6+
* The Noir counterparts are in `noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr`.
7+
*/
8+
export const TXE_ORACLE_VERSION_MAJOR = 1;
9+
export const TXE_ORACLE_VERSION_MINOR = 0;

yarn-project/txe/src/txe_session.test.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,20 @@ describe('TXESession.processFunction', () => {
3232
it('rejects calling a function that does not exist on RPCTranslator with the expected error message', () => {
3333
const invalidName = 'notARealFunction' as unknown as TXEOracleFunctionName;
3434

35-
expect(() => session.processFunction(invalidName, [])).toThrow(
36-
`notARealFunction does not correspond to any oracle handler available on RPCTranslator`,
37-
);
35+
expect(() => session.processFunction(invalidName, [])).toThrow(`Unknown oracle 'notARealFunction'.`);
3836
});
3937

4038
it('rejects calling internal translator helpers (handlerAs*) with the expected error message', () => {
4139
const illegalNames = ['handlerAsMisc', 'handlerAsUtility', 'handlerAsPrivate', 'handlerAsAvm', 'handlerAsTxe'];
4240

4341
for (const name of illegalNames) {
44-
expect(() => session.processFunction(name as any, [])).toThrow(
45-
`${name} does not correspond to any oracle handler available on RPCTranslator`,
46-
);
42+
expect(() => session.processFunction(name as any, [])).toThrow(`Unknown oracle '${name}'.`);
4743
}
4844
});
4945

5046
it("rejects calling the translator's constructor with the expected error message", () => {
5147
const invalidName = 'constructor' as unknown as TXEOracleFunctionName;
5248

53-
expect(() => session.processFunction(invalidName, [])).toThrow(
54-
`constructor does not correspond to any oracle handler available on RPCTranslator`,
55-
);
49+
expect(() => session.processFunction(invalidName, [])).toThrow(`Unknown oracle 'constructor'.`);
5650
});
5751
});

yarn-project/txe/src/txe_session.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { TXEOracleTopLevelContext } from './oracle/txe_oracle_top_level_context.
5353
import { RPCTranslator } from './rpc_translator.js';
5454
import { TXEArchiver } from './state_machine/archiver.js';
5555
import { TXEStateMachine } from './state_machine/index.js';
56+
import { TXE_ORACLE_VERSION_MAJOR, TXE_ORACLE_VERSION_MINOR } from './txe_oracle_version.js';
5657
import type { ForeignCallArgs, ForeignCallResult } from './util/encoding.js';
5758
import { TXEAccountStore } from './util/txe_account_store.js';
5859
import { getSingleTxBlockRequestHash, insertTxEffectIntoWorldTrees, makeTXEBlock } from './utils/block_creation.js';
@@ -109,6 +110,9 @@ export type TXEOracleFunctionName = Exclude<
109110
>;
110111

111112
export interface TXESessionStateHandler {
113+
/** Records the TXE oracle version reported by the Noir test code for diagnostics. */
114+
setTxeOracleVersion(version: { major: number; minor: number }): void;
115+
112116
enterTopLevelState(): Promise<void>;
113117
enterPublicState(contractAddress?: AztecAddress): Promise<void>;
114118
enterPrivateState(contractAddress?: AztecAddress, anchorBlockNumber?: BlockNumber): Promise<PrivateContextInputs>;
@@ -186,6 +190,7 @@ export class TXESession implements TXESessionStateHandler {
186190
private state: SessionState = { name: 'TOP_LEVEL' };
187191
private authwits: Map<string, AuthWitness> = new Map();
188192
private lastCallInfo: LastCallState = emptyLastCallState();
193+
private txeOracleVersion: { major: number; minor: number } | undefined;
189194

190195
constructor(
191196
private logger: Logger,
@@ -306,7 +311,28 @@ export class TXESession implements TXESessionStateHandler {
306311
return translator[validatedFunctionName](...inputs);
307312
} catch (error) {
308313
if (error instanceof z.ZodError) {
309-
throw new Error(`${functionName} does not correspond to any oracle handler available on RPCTranslator`);
314+
let versionHint: string;
315+
if (!this.txeOracleVersion) {
316+
versionHint =
317+
' The test appears to use an older version of Aztec.nr that does not' +
318+
' support test environment oracle versioning. Update Aztec.nr to a compatible version.' +
319+
' See https://docs.aztec.network/errors/12';
320+
} else if (this.txeOracleVersion.minor > TXE_ORACLE_VERSION_MINOR) {
321+
versionHint =
322+
` The test uses Aztec.nr test oracle version` +
323+
` ${this.txeOracleVersion.major}.${this.txeOracleVersion.minor}, but this test environment` +
324+
` only supports up to ${TXE_ORACLE_VERSION_MAJOR}.${TXE_ORACLE_VERSION_MINOR}.` +
325+
` Upgrade the Aztec CLI to a compatible version.` +
326+
` See https://docs.aztec.network/errors/12`;
327+
} else {
328+
versionHint =
329+
` The test's oracle version (${this.txeOracleVersion.major}.${this.txeOracleVersion.minor})` +
330+
` is compatible with this test environment` +
331+
` (${TXE_ORACLE_VERSION_MAJOR}.${TXE_ORACLE_VERSION_MINOR}), so this oracle should be` +
332+
` available. This is an unexpected error, please report it.` +
333+
` See https://docs.aztec.network/errors/13`;
334+
}
335+
throw new Error(`Unknown oracle '${functionName}'.${versionHint}`);
310336
} else if (error instanceof Error) {
311337
throw new Error(
312338
`Execution error while processing function ${functionName} in state ${this.state.name}: ${error.message}`,
@@ -375,6 +401,11 @@ export class TXESession implements TXESessionStateHandler {
375401
return { txHash, anchorBlockTimestamp };
376402
}
377403

404+
setTxeOracleVersion(version: { major: number; minor: number }): void {
405+
this.txeOracleVersion = version;
406+
this.logger.debug(`Test compiled with test oracle version ${version.major}.${version.minor}`);
407+
}
408+
378409
async enterTopLevelState() {
379410
switch (this.state.name) {
380411
case 'PRIVATE': {

0 commit comments

Comments
 (0)