Skip to content
Merged
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
2 changes: 1 addition & 1 deletion yarn-project/acir-simulator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@aztec/circuits.js": "workspace:^",
"@aztec/foundation": "workspace:^",
"@aztec/types": "workspace:^",
"acvm_js": "github:noir-lang/acvm-js-wasm#arv/0.24.1",
"acvm_js": "github:noir-lang/acvm-js-wasm#arv/0.25.0",
"levelup": "^5.1.1",
"memdown": "^6.1.1",
"tslib": "^2.4.0"
Expand Down
108 changes: 45 additions & 63 deletions yarn-project/acir-simulator/src/acvm/acvm.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { FunctionDebugMetadata } from '@aztec/foundation/abi';
import { FunctionDebugMetadata, OpcodeLocation } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
import { NoirCallStack } from '@aztec/types';
import { NoirCallStack, SourceCodeLocation } from '@aztec/types';

import {
ExecutionError,
ForeignCallInput,
ForeignCallOutput,
WasmBlackBoxFunctionSolver,
Expand Down Expand Up @@ -70,18 +71,14 @@ export interface ACIRExecutionResult {
partialWitness: ACVMWitness;
}

/**
* Extracts the opcode location from an ACVM error string.
*/
function extractOpcodeLocationFromError(err: string): string | undefined {
const match = err.match(/^Cannot satisfy constraint (?<opcodeLocation>[0-9]+(?:\.[0-9]+)?)/);
return match?.groups?.opcodeLocation;
}

/**
* Extracts the call stack from the location of a failing opcode and the debug metadata.
* One opcode can point to multiple calls due to inlining.
*/
function getCallStackFromOpcodeLocation(opcodeLocation: string, debug: FunctionDebugMetadata): NoirCallStack {
function getSourceCodeLocationsFromOpcodeLocation(
opcodeLocation: string,
debug: FunctionDebugMetadata,
): SourceCodeLocation[] {
const { debugSymbols, files } = debug;

const callStack = debugSymbols.locations[opcodeLocation] || [];
Expand All @@ -92,45 +89,32 @@ function getCallStackFromOpcodeLocation(opcodeLocation: string, debug: FunctionD

const locationText = source.substring(span.start, span.end + 1);
const precedingText = source.substring(0, span.start);
const line = precedingText.split('\n').length;
const previousLines = precedingText.split('\n');
// Lines and columns in stacks are one indexed.
const line = previousLines.length;
const column = previousLines[previousLines.length - 1].length + 1;

return {
filePath: path,
line,
column,
fileSource: source,
locationText,
};
});
}

/**
* Extracts source code locations from an ACVM error if possible.
* @param errMessage - The ACVM error.
* Extracts the source code locations for an array of opcode locations
* @param opcodeLocations - The opcode locations that caused the error.
* @param debug - The debug metadata of the function.
* @returns The source code locations or undefined if they couldn't be extracted from the error.
* @returns The source code locations.
*/
export function processAcvmError(errMessage: string, debug: FunctionDebugMetadata): NoirCallStack | undefined {
const opcodeLocation = extractOpcodeLocationFromError(errMessage);
if (!opcodeLocation) {
return undefined;
}

return getCallStackFromOpcodeLocation(opcodeLocation, debug);
}

/**
* An error thrown by the ACVM during simulation. Optionally contains a noir call stack.
*/
export class ACVMError extends Error {
constructor(
message: string,
/**
* The noir call stack of the error, if it could be extracted.
*/
public callStack?: NoirCallStack,
) {
super(message);
}
export function resolveOpcodeLocations(
opcodeLocations: OpcodeLocation[],
debug: FunctionDebugMetadata,
): SourceCodeLocation[] {
return opcodeLocations.flatMap(opcodeLocation => getSourceCodeLocationsFromOpcodeLocation(opcodeLocation, debug));
}

/**
Expand All @@ -141,13 +125,8 @@ export async function acvm(
acir: Buffer,
initialWitness: ACVMWitness,
callback: ACIRCallback,
debug?: FunctionDebugMetadata,
): Promise<ACIRExecutionResult> {
const logger = createDebugLogger('aztec:simulator:acvm');
// This is a workaround to avoid the ACVM removing the information about the underlying error.
// We should probably update the ACVM to let proper errors through.
let oracleError: Error | undefined = undefined;

const partialWitness = await executeCircuitWithBlackBoxSolver(
solver,
acir,
Expand All @@ -169,31 +148,34 @@ export async function acvm(
} else {
typedError = new Error(`Error in oracle callback ${err}`);
}
oracleError = typedError;
logger.error(`Error in oracle callback ${name}:`, typedError.message, typedError.stack);
logger.error(`Error in oracle callback ${name}`);
throw typedError;
}
},
).catch((acvmErrorString: string) => {
if (oracleError) {
throw oracleError;
}

if (debug) {
const callStack = processAcvmError(acvmErrorString, debug);

if (callStack) {
throw new ACVMError(
`Assertion failed: '${callStack[callStack.length - 1]?.locationText ?? 'Unknown'}'`,
callStack,
);
}
}
// If we cannot find a callstack, throw the original error.
throw new ACVMError(acvmErrorString);
});
);

return { partialWitness };
}

/**
* Extracts the call stack from an thrown by the acvm.
* @param error - The error to extract from.
* @param debug - The debug metadata of the function called.
* @returns The call stack, if available.
*/
export function extractCallStack(
error: Error | ExecutionError,
debug?: FunctionDebugMetadata,
): NoirCallStack | undefined {
if (!('callStack' in error) || !error.callStack) {
return undefined;
}
const { callStack } = error;
if (!debug) {
return callStack;
}

return Promise.resolve({ partialWitness });
return resolveOpcodeLocations(callStack, debug);
}

/**
Expand Down
Loading