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

Add tracing to t8n #3953

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open

Add tracing to t8n #3953

wants to merge 46 commits into from

Conversation

acolytec3
Copy link
Contributor

@acolytec3 acolytec3 commented Mar 31, 2025

Also closes #3561

This adds JSON tracing to our implementation of t8n in compliance with EIP-7756.

To test

  1. Follow these instructions to install the eest test harness if you haven't already. Check out this commit 2a747431fb91c0be2ed959faad9c84ec9b8a3607
  2. Run uv run fill -k "eip4750_functions and test_callf_execution" tests/osaka --from=Osaka --until=Osaka --evm-bin=../ethjs/packages/vm/test/t8n/ethereumjs-t8ntool.sh --evm-dump-dir=./traces --traces with this fork active from your local folder for the ethereum-spec-tests.
  3. You should see one or more traces in the root directory for the ethereum-spec-tests

These can be compared against geth by just replacing the path of --evm-bin with a local geth instance.

This PR addresses several issues along the way:

  1. Adds new stepTraceJSON/summaryTraceJSON utility methods for converting the contents of the StepTrace and AfterTx object emitted by the evm.events Step and vm.events AfterTx event.
  2. Addresses Inconsistent Memory Expansion Behavior while logging ethereumjs/evm #3561 so we report the pre-expanded memory size in the Step Event.

Copy link

codecov bot commented Mar 31, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.22%. Comparing base (b843695) to head (cc48f0c).
Report is 1 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

Flag Coverage Δ
block 83.34% <ø> (ø)
blockchain 89.33% <ø> (ø)
client ?
common 97.54% <ø> (ø)
devp2p 86.78% <ø> (ø)
evm ?
genesis 99.98% <ø> (ø)
mpt 89.76% <ø> (+0.05%) ⬆️
rlp 91.43% <ø> (ø)
statemanager 69.16% <ø> (ø)
tx 90.59% <ø> (ø)
util 82.02% <ø> (ø)
vm ?
wallet 88.55% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@jochem-brouwer jochem-brouwer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start! Some initial comments 😄 👍

@@ -84,7 +89,7 @@ export function getArguments() {
reward: BigInt((<any>args)['state.reward']),
chainid: BigInt((<any>args)['state.chainid']),
}

args.trace = (<any>args)['trace']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for myself: I should update these types, not sure why the types are not cast or set. Should do something similar as in the client where the CLI arguments are correctly typed (not this PR)

}
trace.push(JSON.stringify(summary))
writeFileSync(
`trace-${index}-${bytesToHex(event.transaction.hash())}.json`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be written to the output dir args.output.basedir (maybe even in a dedicated trace folder, take a look what Geth does in this case if they have a specific trace folder or just dump all in the output dir). Also note that in the current setup it will depend on from which path the t8ntool is being called (will write to current path, not something relative to it).

`trace-${index}-${bytesToHex(event.transaction.hash())}.json`,
`[${trace.join(',\n')}]`,
)
trace = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic could be moved to this.afterTx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've debated about this. I kind of like all the trace logic being in one spot so it's easy to see what's going on. Could be persuaded otherwise though.

@@ -218,10 +253,10 @@ export class TransitionTool {
if (args.log === true) {
this.vm.events.on('beforeTx', (_, resolve) => {
// eslint-disable-next-line no-console
console.log('Processing new transaction...')
if (args.log === true) console.log('Processing new transaction...')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrapped inside an if (args.log === true) and I don't think the log flag can change, so I believe this is not necessary?

@acolytec3 acolytec3 added package: evm type: test all hardforks This special label enables VM state and blockchain tests for all hardforks on the respective PR. labels Apr 3, 2025
Copy link
Member

@jochem-brouwer jochem-brouwer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments! CI also fails.

@@ -58,7 +58,7 @@ export {
RustBN254,
validateEOF,
}

export { JSONifyStepTrace } from './opcodes/util.ts'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to rename this to StepTraceJSON. Why is this exported?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use it in t8n and that's in vm 🙃

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yer its fine, sorry for some reason I thought it was a type and not a method, and AFAIK you dont have to explicitly export types in order to be picked up by the IDE

npx tsx --conditions=typescript "$SCRIPT_DIR/launchT8N.ts" "$@"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep this flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it since most of your edits will be in evm but this lives in the vm. Otherwise, you have to rebuild evm every time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes sorry my question should have gotten more context: this will thus only work on node versions where this flag is available. I'm not sure about this one, this means that people need to have node v22 installed (right?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the conditions flag was introduced in either 18 or 20

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, lets keep! 😄 👍

Copy link
Member

@jochem-brouwer jochem-brouwer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some points will directly address

@acolytec3 acolytec3 marked this pull request as ready for review April 7, 2025 19:40
Copy link
Member

@jochem-brouwer jochem-brouwer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, some comments! 😄 👍

}

for (let i = 0; i < this.codeSizes.length; i++) {
if (codePosition < this.codeStartPos[i] + this.codeSizes[i]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only works if the codeStartPos cache is initialized for this index. (See getCodePosition(section: number) {, this is called for EOF opcodes like CALLF and JUMPF).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this here? Seems like it would make sense to do so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would normally make a lot of sense, but we want to reduce the amount of code inside the EVM as much as possible. So, we do not want to initialize the entire code start position container each time we enter a new EVM frame (only want to calculate it when necessary). (This is the same as the JUMPDEST calculation, we only do this when necessary i.e. we hit a JUMP opcode)

* @property {number} section the current EOF code section referenced by the PC
* @property {Uint8Array} immediate the immediate argument of the opcode
* @property {Uint8Array} error the error data of the opcode (only present for REVERT)
* @property {number} functionDepth the depth of the function call (only present for EOF)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storage is not added here yet

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it, is this documentation necessary? Doesn't a dev immediately get the docs from the emitted event type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered about that. I think we need to move all of this to the InterpreterStep interface (or just remove it)

@@ -271,3 +272,24 @@ export function updateSstoreGas(
return common.param('sstoreSetGas')
}
}

export const getImmediate = (opcode: number, code: Uint8Array, pc: number) => {
let immediate: Uint8Array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for this, use the eof/stackdelta.ts and then the immediate value. The only exception is RJUMPV, which is thus dynamic. (Immediates depend upon the table length)

* @param memory whether to include the memory in the trace
* @returns a JSON object that matches the EIP-7756 trace format
*/
export const stepTraceJSON = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we directly JSON.stringify the output of the step event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, was on the fence about whether to do that or not since it would return a string instead of a JSON object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: evm package: vm type: test all hardforks This special label enables VM state and blockchain tests for all hardforks on the respective PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Inconsistent Memory Expansion Behavior while logging ethereumjs/evm
3 participants