-
Notifications
You must be signed in to change notification settings - Fork 628
Add opcode tracing plugin #10038
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
base: master
Are you sure you want to change the base?
Add opcode tracing plugin #10038
Conversation
LukaszRozmej
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't check everything, but things that potentially stand out:
- Not sure if converting to
stringis done too soon. IMO whole trace could be by domain types (byte, Hash256), and stringify only when outputing JSON - could be faster - Paths manipulation - should be standarised with other in node
- File writing - avoid serializing to string first, directly to file.
| "Nethermind.Api": "[1.36.0-unstable, )", | ||
| "Nethermind.Blockchain": "[1.36.0-unstable, )", | ||
| "Nethermind.Config": "[1.36.0-unstable, )", | ||
| "Nethermind.Core": "[1.36.0-unstable, )", | ||
| "Nethermind.Evm": "[1.36.0-unstable, )", | ||
| "Nethermind.Logging": "[1.36.0-unstable, )", | ||
| "Nethermind.Synchronization": "[1.36.0-unstable, )" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably be 1.37.0-unstable
| /// <summary> | ||
| /// Gets or sets a value indicating whether the OpcodeTracing plugin is enabled. | ||
| /// </summary> | ||
| [ConfigItem(Description = "Enable the OpcodeTracing plugin.", DefaultValue = "false")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| [ConfigItem(Description = "Enable the OpcodeTracing plugin.", DefaultValue = "false")] | |
| [ConfigItem(Description = "Whether to enable opcode tracing.", DefaultValue = "false")] |
| /// <summary> | ||
| /// Gets or sets the directory where opcode trace JSON files are written. | ||
| /// </summary> | ||
| [ConfigItem(Description = "Directory where opcode trace JSON files are written.", DefaultValue = "traces/opcodes")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| [ConfigItem(Description = "Directory where opcode trace JSON files are written.", DefaultValue = "traces/opcodes")] | |
| [ConfigItem(Description = "Directory of opcode trace JSON files to be written.", DefaultValue = "traces/opcodes")] |
| /// Gets or sets the opcode counts dictionary mapping opcode names to occurrence counts. | ||
| /// </summary> | ||
| [JsonPropertyName("opcodeCounts")] | ||
| public required Dictionary<string, long> OpcodeCounts { get; init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would probably be faster to accumulate if it was
| public required Dictionary<string, long> OpcodeCounts { get; init; } | |
| public required Dictionary<byte, long> OpcodeCounts { get; init; } |
And we can add JsonConverter to serialize it later as string (or just convert to <string, long> dictionary
| /// <returns>The full path to the created file, or null if writing failed.</returns> | ||
| public async Task<string?> WriteAsync(string outputDirectory, TraceOutput traceOutput) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(outputDirectory)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to use PathUtils.GetApplicationResourcePath to use relative/absolute paths better and potentially avoid issues with environments with restricted access
| /// Gets or sets the opcode counts dictionary mapping opcode names to occurrence counts for this block. | ||
| /// </summary> | ||
| [JsonPropertyName("opcodeCounts")] | ||
| public required Dictionary<string, long> OpcodeCounts { get; init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| public required Dictionary<string, long> OpcodeCounts { get; init; } | |
| public required Dictionary<byte, long> OpcodeCounts { get; init; } |
| labels[i] = Enum.IsDefined(typeof(Instruction), opcode) | ||
| ? ((Instruction)opcode).ToString() | ||
| : $"0x{opcode:x2}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use F
| labels[i] = Enum.IsDefined(typeof(Instruction), opcode) | |
| ? ((Instruction)opcode).ToString() | |
| : $"0x{opcode:x2}"; | |
| labels[i] = FastEnum.IsDefined(typeof(Instruction), opcode) | |
| ? FastEnum.ToString((Instruction)opcode) | |
| : $"0x{opcode:x2}"; |
| if (traceOutput is null) | ||
| { | ||
| if (_logger.IsError) | ||
| { | ||
| _logger.Error("Cumulative trace output is null"); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| // Ensure directory exists | ||
| if (!Directory.Exists(_outputDirectory)) | ||
| { | ||
| Directory.CreateDirectory(_outputDirectory); | ||
| } | ||
|
|
||
| // Serialize and write (overwrite existing file) | ||
| string json = JsonSerializer.Serialize(traceOutput, _serializerOptions); | ||
| await File.WriteAllTextAsync(_filePath!, json).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
| /// Gets or sets the aggregated opcode counts dictionary mapping opcode names to total occurrence counts. | ||
| /// </summary> | ||
| [JsonPropertyName("opcodeCounts")] | ||
| public required Dictionary<string, long> OpcodeCounts { get; init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| public required Dictionary<string, long> OpcodeCounts { get; init; } | |
| public required Dictionary<byte, long> OpcodeCounts { get; init; } |
| BlockHash = blockHash.ToString(), | ||
| ParentHash = parentHash.ToString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep it as Hash256
|
@LukaszRozmej thank you for the review. I think I fixed everything, please take a look |
3b99ef7 to
dfab3a4
Compare
Changes
--OpcodeTracing.Enabled,--OpcodeTracing.Mode,--OpcodeTracing.Blocks,--OpcodeTracing.StartBlock,--OpcodeTracing.EndBlock,--OpcodeTracing.OutputDirectory,--OpcodeTracing. MaxDegreeOfParallelism(for retrospective mode only).Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Notes on testing
Tested manually via Docker with Hoodi testnet:
See attached logs json files.
opcode-trace-block-1845471.json
opcode-trace-block-1845470.json
opcode-trace-all-20251224143649.json
opcode-trace-1844925-1844934.json
05-validation-error.log
04-realtime-mode.log
03-retrospective-mode.log
02-plugin-not-specified.log
01-plugin-disabled.log
Documentation
Requires documentation update
Usage examples needed in docs for CLI flags and output format.
I also added detailed documentation how it works at
src/Nethermind/Nethermind.OpcodeTracing.Plugin/README.md.Requires explanation in Release Notes
We can add something like: New optional plugin for tracing EVM opcode usage. Enable with
--OpcodeTracing.Enabled=true. Useful for analyzing opcode frequency across block ranges.