Skip to content

Commit

Permalink
feat: Add support for assert messages & runtime call stacks (#1997)
Browse files Browse the repository at this point in the history
Resolves #1814
Resolves #1815
  • Loading branch information
sirasistant authored Sep 5, 2023
1 parent cec901f commit ac68837
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 336 deletions.
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

0 comments on commit ac68837

Please sign in to comment.