Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up the VMTracer and trace events interface in TS #5429

Merged
merged 6 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ import type {
import type {
EdrContext,
Provider as EdrProviderT,
ExecutionResult,
RawTrace,
Response,
SubscriptionEvent,
TracingMessage,
TracingStep,
} from "@nomicfoundation/edr";
import { Common } from "@nomicfoundation/ethereumjs-common";
import chalk from "chalk";
Expand Down Expand Up @@ -152,12 +149,6 @@ export function getNodeConfig(
};
}

export interface RawTraceCallbacks {
onStep?: (messageTrace: TracingStep) => Promise<void>;
onBeforeMessage?: (messageTrace: TracingMessage) => Promise<void>;
onAfterMessage?: (messageTrace: ExecutionResult) => Promise<void>;
}

class EdrProviderEventAdapter extends EventEmitter {}

type CallOverrideCallback = (
Expand All @@ -176,6 +167,9 @@ export class EdrProviderWrapper
// temporarily added to make smock work with HH+EDR
private _callOverrideCallback?: CallOverrideCallback;

/** Used for internal stack trace tests. */
private _vmTracer?: VMTracer;

private constructor(
private readonly _provider: EdrProviderT,
// we add this for backwards-compatibility with plugins like solidity-coverage
Expand All @@ -184,7 +178,6 @@ export class EdrProviderWrapper
},
private readonly _eventAdapter: EdrProviderEventAdapter,
private readonly _vmTraceDecoder: VmTraceDecoder,
private readonly _rawTraceCallbacks: RawTraceCallbacks,
// The common configuration for EthereumJS VM is not used by EDR, but tests expect it as part of the provider.
private readonly _common: Common,
tracingConfig?: TracingConfig
Expand All @@ -199,7 +192,6 @@ export class EdrProviderWrapper
public static async create(
config: HardhatNetworkProviderConfig,
loggerConfig: LoggerConfig,
rawTraceCallbacks: RawTraceCallbacks,
tracingConfig?: TracingConfig
): Promise<EdrProviderWrapper> {
const { Provider } = requireNapiRsModule(
Expand Down Expand Up @@ -325,7 +317,6 @@ export class EdrProviderWrapper
minimalEthereumJsNode,
eventAdapter,
vmTraceDecoder,
rawTraceCallbacks,
common,
tracingConfig
);
Expand Down Expand Up @@ -371,9 +362,7 @@ export class EdrProviderWrapper
const needsTraces =
this._node._vm.evm.events.eventNames().length > 0 ||
this._node._vm.events.eventNames().length > 0 ||
this._rawTraceCallbacks.onStep !== undefined ||
this._rawTraceCallbacks.onAfterMessage !== undefined ||
this._rawTraceCallbacks.onBeforeMessage !== undefined;
this._vmTracer !== undefined;

if (needsTraces) {
const rawTraces = responseObject.traces;
Expand All @@ -394,9 +383,8 @@ export class EdrProviderWrapper
edrTracingStepToMinimalInterpreterStep(traceItem)
);
}
if (this._rawTraceCallbacks.onStep !== undefined) {
await this._rawTraceCallbacks.onStep(traceItem);
}

this._vmTracer?.addStep(traceItem);
}
// afterMessage event
else if ("executionResult" in traceItem) {
Expand All @@ -406,11 +394,8 @@ export class EdrProviderWrapper
edrTracingMessageResultToMinimalEVMResult(traceItem)
);
}
if (this._rawTraceCallbacks.onAfterMessage !== undefined) {
await this._rawTraceCallbacks.onAfterMessage(
traceItem.executionResult
);
}

this._vmTracer?.addAfterMessage(traceItem.executionResult);
}
// beforeMessage event
else {
Expand All @@ -420,9 +405,8 @@ export class EdrProviderWrapper
edrTracingMessageToMinimalMessage(traceItem)
);
}
if (this._rawTraceCallbacks.onBeforeMessage !== undefined) {
await this._rawTraceCallbacks.onBeforeMessage(traceItem);
}

this._vmTracer?.addBeforeMessage(traceItem);
}
}

Expand Down Expand Up @@ -484,6 +468,15 @@ export class EdrProviderWrapper
}
}

/**
* Injects a `VMTracer` that observes EVM throughout requests.
*
* Used for internal stack traces integration tests.
*/
public injectVmTracer(vmTracer?: VMTracer) {
Wodann marked this conversation as resolved.
Show resolved Hide resolved
this._vmTracer = vmTracer;
}

// temporarily added to make smock work with HH+EDR
private _setCallOverrideCallback(callback: CallOverrideCallback) {
this._callOverrideCallback = callback;
Expand Down Expand Up @@ -583,16 +576,16 @@ export class EdrProviderWrapper
private async _rawTraceToSolidityStackTrace(
rawTrace: RawTrace
): Promise<SolidityStackTrace | undefined> {
const vmTracer = new VMTracer(false);
const vmTracer = new VMTracer();

const trace = rawTrace.trace();
for (const traceItem of trace) {
if ("pc" in traceItem) {
await vmTracer.addStep(traceItem);
vmTracer.addStep(traceItem);
} else if ("executionResult" in traceItem) {
await vmTracer.addAfterMessage(traceItem.executionResult);
vmTracer.addAfterMessage(traceItem.executionResult);
} else {
await vmTracer.addBeforeMessage(traceItem);
vmTracer.addBeforeMessage(traceItem);
}
}

Expand Down Expand Up @@ -634,7 +627,6 @@ export async function createHardhatNetworkProvider(
return EdrProviderWrapper.create(
hardhatNetworkProviderConfig,
loggerConfig,
{},
await makeTracingConfig(artifacts)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,4 @@ export class Exit {

const _exhaustiveCheck: never = this.kind;
}

public getEdrExceptionalHalt(): ExceptionalHalt {
const { ExceptionalHalt } = requireNapiRsModule(
"@nomicfoundation/edr"
) as typeof import("@nomicfoundation/edr");

switch (this.kind) {
case ExitCode.OUT_OF_GAS:
return ExceptionalHalt.OutOfGas;
case ExitCode.INVALID_OPCODE:
return ExceptionalHalt.OpcodeNotFound;
case ExitCode.CODESIZE_EXCEEDS_MAXIMUM:
return ExceptionalHalt.CreateContractSizeLimit;
case ExitCode.CREATE_COLLISION:
return ExceptionalHalt.CreateCollision;

default:
// eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error
throw new Error(`Unmatched exit code: ${this.kind}`);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export interface PrecompileMessageTrace extends BaseMessageTrace {

export interface BaseEvmMessageTrace extends BaseMessageTrace {
code: Uint8Array;
value: bigint;
returnData: Uint8Array;
steps: MessageTraceStep[];
bytecode?: Bytecode;
// The following is just an optimization: When processing this traces it's useful to know ahead of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ import {
const DUMMY_RETURN_DATA = Buffer.from([]);
const DUMMY_GAS_USED = 0n;

/**
* Consumes the incoming VM trace events, until an error occurs, to keep track
* of the last top level message trace/error.
*/
export class VMTracer {
public tracingSteps: TracingStep[] = [];

private _messageTraces: MessageTrace[] = [];
private _lastError: Error | undefined;
private _maxPrecompileNumber;

constructor(private readonly _throwErrors = true) {
constructor() {
// TODO: temporarily hardcoded to remove the need of using ethereumjs' common and evm here
this._maxPrecompileNumber = 10;
}
Expand All @@ -46,15 +50,11 @@ export class VMTracer {
return this._lastError;
}

public clearLastError() {
this._lastError = undefined;
}

private _shouldKeepTracing() {
return this._throwErrors || this._lastError === undefined;
return this._lastError === undefined;
}

public async addBeforeMessage(message: TracingMessage) {
public addBeforeMessage(message: TracingMessage) {
if (!this._shouldKeepTracing()) {
return;
}
Expand Down Expand Up @@ -143,15 +143,11 @@ export class VMTracer {

this._messageTraces.push(trace);
} catch (error) {
if (this._throwErrors) {
throw error;
} else {
this._lastError = error as Error;
}
this._lastError = error as Error;
}
}

public async addStep(step: TracingStep) {
public addStep(step: TracingStep) {
if (!this._shouldKeepTracing()) {
return;
}
Expand All @@ -169,15 +165,11 @@ export class VMTracer {

trace.steps.push({ pc: Number(step.pc) });
} catch (error) {
if (this._throwErrors) {
throw error;
} else {
this._lastError = error as Error;
}
this._lastError = error as Error;
}
}

public async addAfterMessage(result: ExecutionResult, haltOverride?: Exit) {
public addAfterMessage(result: ExecutionResult) {
if (!this._shouldKeepTracing()) {
return;
}
Expand All @@ -197,8 +189,7 @@ export class VMTracer {
).address;
}
} else if (isHaltResult(executionResult)) {
trace.exit =
haltOverride ?? Exit.fromEdrExceptionalHalt(executionResult.reason);
trace.exit = Exit.fromEdrExceptionalHalt(executionResult.reason);

trace.returnData = Buffer.from([]);
} else {
Expand All @@ -211,11 +202,7 @@ export class VMTracer {
this._messageTraces.pop();
}
} catch (error) {
if (this._throwErrors) {
throw error;
} else {
this._lastError = error as Error;
}
this._lastError = error as Error;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const senderAddress = bytesToHex(privateToAddress(toBuffer(senderPrivateKey)));
export async function instantiateProvider(
loggerConfig: LoggerConfig,
tracingConfig: TracingConfig
): Promise<[EdrProviderWrapper, VMTracer]> {
): Promise<EdrProviderWrapper> {
const config = {
hardfork: "shanghai",
chainId: 1,
Expand All @@ -55,26 +55,13 @@ export async function instantiateProvider(
enableTransientStorage: false,
};

const vmTracer = new VMTracer(false);

const provider = await EdrProviderWrapper.create(
config,
loggerConfig,
{
onStep: async (step) => {
await vmTracer.addStep(step);
},
onAfterMessage: async (message) => {
await vmTracer.addAfterMessage(message);
},
onBeforeMessage: async (message) => {
await vmTracer.addBeforeMessage(message);
},
},
tracingConfig
);

return [provider, vmTracer];
return provider;
}

export function encodeConstructorParams(
Expand Down Expand Up @@ -116,9 +103,11 @@ export interface TxData {

export async function traceTransaction(
provider: EdrProviderWrapper,
vmTracer: VMTracer,
txData: TxData
): Promise<MessageTrace> {
const vmTracer = new VMTracer();
provider.injectVmTracer(vmTracer);

try {
await provider.request({
method: "eth_sendTransaction",
Expand All @@ -142,6 +131,6 @@ export async function traceTransaction(
}
return trace;
} finally {
vmTracer.clearLastError();
provider.injectVmTracer(undefined);
}
}
Loading
Loading