-
Notifications
You must be signed in to change notification settings - Fork 25
Normalise rpc debug trace format #839
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: main
Are you sure you want to change the base?
Normalise rpc debug trace format #839
Conversation
Geth is used to generate the desired JSON trace format and these tests validates the results against them.
🦋 Changeset detectedLatest commit: 090729a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@nebasuke This is my PR for the issue. |
Great, thank you @AmosAidoo, we appreciate the effort! It will probably be @Wodann or @agostbiro that can take a look. |
|
Great, thanks! I'll re-run CI when needed. |
additionally added pass and gasUsed fields and leading 0x for storage and memory as the hardhat tests still expects them
the tests still expect the |
You can re-run the CI |
Can you kindly re-run |
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.
Thank you for taking a stab at this. I left some requests to try and move the changes into edr_provider
as the current approach goes through multiple steps of serialization and deserialization.
crates/edr_napi/src/debug_trace.rs
Outdated
pub struct RpcDebugTraceResult { | ||
pub failed: bool, | ||
pub gas: BigInt, | ||
pub pass: bool, | ||
pub gas_used: BigInt, | ||
pub return_value: String, | ||
pub struct_logs: Vec<RpcDebugTraceLogItem>, | ||
} |
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.
Could you please add documentation for the struct and its pub
member fields?
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.
Done
crates/edr_napi/src/provider.rs
Outdated
@@ -102,6 +105,9 @@ impl Provider { | |||
#[doc = "Handles a JSON-RPC request and returns a JSON-RPC response."] | |||
#[napi] | |||
pub async fn handle_request(&self, json_request: String) -> napi::Result<Response> { | |||
let method_name = serde_json::from_str::<JsonRpcMethodExtractor>(&json_request) | |||
.map_or_else(|_| "".to_string(), |m| m.method); |
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.
.map_or_else(|_| "".to_string(), |m| m.method); | |
.map_or_else(|_| String::new(), |m| m.method); |
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.
Removed this as well
crates/edr_napi/src/provider.rs
Outdated
let parsed_data: Result<DebugTraceResult, _> = match &data { | ||
Either::A(json_string) => { | ||
serde_json::from_str::<JsonRpcResponse<DebugTraceResult>>(json_string) | ||
.map(|r| r.result) | ||
} | ||
Either::B(json_value) => serde_json::from_value::< | ||
JsonRpcResponse<DebugTraceResult>, | ||
>(json_value.clone()) | ||
.map(|r| r.result), | ||
}; | ||
|
||
match parsed_data { | ||
Ok(trace_result) => { | ||
let transformed = normalise_rpc_debug_trace(trace_result) | ||
.map_err(|e| napi::Error::new(Status::GenericFailure, e))?; | ||
|
||
let transformed = JsonRpcResponse { | ||
result: transformed, | ||
}; | ||
Either::B(serde_json::to_value(transformed)?) | ||
} | ||
Err(_) => data, | ||
} |
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.
Could you please split this into a function with a descriptive name?
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.
No need for these lines now after moving normalisation to edr_provider
.
crates/edr_napi/src/provider.rs
Outdated
let parsed_data: Result<DebugTraceResult, _> = match &data { | ||
Either::A(json_string) => { | ||
serde_json::from_str::<JsonRpcResponse<DebugTraceResult>>(json_string) | ||
.map(|r| r.result) | ||
} | ||
Either::B(json_value) => serde_json::from_value::< | ||
JsonRpcResponse<DebugTraceResult>, | ||
>(json_value.clone()) | ||
.map(|r| r.result), | ||
}; |
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 is incredibly inefficient to:
- serialize the result to JSON (string or object)
- deserialize the result from JSON (string or object)
- normalize result
- serialize the result
Could you please investigate making this change one level up, before we first serialize the result? That would be in edr_provider
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.
I have moved the normalisation into edr_provider
Perfect, thank you for the review. I'll make the changes. |
I made the necessary changes. Tests pass when I run them locally. Could you kindly re-trigger the ci. |
Seems to be looking good so far regarding the tests. Looks like the remaining failures are because of environmental variables not set as I am an external contributor. |
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.
This is going in the right direction, but quite a few things are still unclear to me about the chosen solution. I'd appreciate if you could clarify.
crates/edr_napi/src/debug_trace.rs
Outdated
/// together because Hardhat still depends on them but `pass` and `gas_used` | ||
/// should likely to be removed after a while. | ||
#[napi(object)] | ||
pub struct RpcDebugTraceResult { |
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.
As this type is no longer part of the API, do we really need to expose it?
I assume Hardhat has its own definition, given that it's a standard.
cc: @fvictorio
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 are right, Hardhat has its own definition
crates/edr_napi/src/debug_trace.rs
Outdated
/// Debug logs after normalising the EIP-3155 debug logs. | ||
/// This is the format Hardhat expects | ||
#[napi(object)] | ||
pub struct RpcDebugTraceLogItem { |
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.
As this type is no longer part of the API, do we really need to expose it?
I assume Hardhat has its own definition, given that it's a standard.
cc: @fvictorio
crates/edr_napi/test/provider.ts
Outdated
@@ -415,3 +515,48 @@ function assertEqualMemory(stepMemory: Buffer | undefined, expected: Buffer) { | |||
|
|||
assert.isTrue(stepMemory.equals(expected)); | |||
} | |||
|
|||
function assertJsonRpcFormatNormalised(trace: RpcDebugTraceResult) { |
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.
I think these assertions can happen without knowing the type RpcDebugTraceResult
, but am not 100%. If so, we can just remove the type from EDR's N-API, reducing our API surface.
// assert.isFalse(key.startsWith("0x")); | ||
// assert.isFalse(value.startsWith("0x")); |
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.
Why are these two disabled?
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.
I disabled these two because at this moment, the normalisation is still being done in Hardhat so it is still correct to have 0x
at the start of the strings.
Here is the reference in Hardhat: https://github.com/NomicFoundation/hardhat/blob/024d72b09c6edefb00c012e9514a0948c255d0ab/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts#L197-L203
The Hardhat tests also operate on this assumption so I believe these two asserts should be disabled until the normalisation is completely removed from Hardhat.
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.
There is a process for incorporating changes to Hardhat into your PR. It's explained here. That's what would be required in order to test your changes in Hardhat and make sure everything is working as expected.
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.
Ah perfect. Thank you
crates/edr_provider/src/lib.rs
Outdated
@@ -380,10 +382,12 @@ impl<LoggerErrorT: Debug + Send + Sync + 'static, TimerT: Clone + TimeSinceEpoch | |||
// debug_* methods | |||
MethodInvocation::DebugTraceTransaction(transaction_hash, config) => { | |||
debug::handle_debug_trace_transaction(data, transaction_hash, config) | |||
.and_then(normalise_rpc_debug_trace) |
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.
Please move this call inside the handle_debug_trace_transaction
call, following the same pattern as all other functions. The handle_*
functions determine the result type, not the provider.
crates/edr_provider/src/lib.rs
Outdated
|
||
// Rust port of https://github.com/NomicFoundation/hardhat/blob/024d72b09c6edefb00c012e9514a0948c255d0ab/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts#L176 | ||
/// This normalization is done because this is the format Hardhat expects | ||
fn normalise_rpc_debug_trace<LoggerErrorT: Debug>( |
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.
Please move this to the same file as the handle_
function I mentioned in my other comment.
crates/edr_provider/src/lib.rs
Outdated
// Removing this trim temporarily as the Hardhat test assumes 0x is there | ||
// .map(|value| value.trim_start_matches("0x").to_string()) |
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.
If Hardhat requires the 0x
, then why do you believe we should trim the 0x
?
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.
Hardhat doesn't require the 0x
but assumes they are there. Hardhat later on trims the 0x
when normalising. Here is the relevant code:
https://github.com/NomicFoundation/hardhat/blob/024d72b09c6edefb00c012e9514a0948c255d0ab/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts#L192-L195
When running the hardhart-tests
for example, I confirmed this expected format from the trace generated from Geth which the tests depend on:
crates/edr_provider/src/lib.rs
Outdated
storage: log.storage.map(|storage| { | ||
storage | ||
.into_iter() | ||
// Removing this trim temporarily as the Hardhat test assumes 0x is there |
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.
If Hardhat requires the 0x
, then why do you believe we should trim the 0x
?
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 as the reasons stated for the two related comments above. Hardhat doesn't require 0x
but assumes it is there and so it get removed during normalisation.
@@ -120,3 +121,48 @@ impl From<DebugTraceConfig> for edr_evm::DebugTraceConfig { | |||
} | |||
} | |||
} | |||
|
|||
/// This is the JSON-RPC Debug trace format | |||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] |
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.
Do we need both Deserialize
and Serialize
? Based on the code, it seems that serde::Serialize
is sufficient.
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.
Yes you are right, thanks for pointing it out. I'll remove it.
#[serde(rename_all = "camelCase")] | ||
pub struct RpcDebugTraceResult { | ||
pub failed: bool, | ||
pub gas: u64, |
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.
Did the old format require a 0x...
representation? What made you decide to go with normal u64
serialization?
Do you have a reference to support this choice?
If we should support a hex representation of a quantity, you should use alloy_serde::quantity
.
This applies to all u64
usages here.
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.
Yes so I used u64
because gas_used
in DebugTraceResult
is also u64
:
https://github.com/NomicFoundation/edr/blob/main/crates/edr_evm/src/debug_trace.rs#L220
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.
Noted the point on alloy_serde::quantity
for hex quantities.
Remove unused serde::Deserialize from rpc debug structs and move normalization into debug.rs, following the pattern of other functions in the provider
I have made some changes and replied to the questions |
I added some information to the original issue on how to create test vectors for your code. You can essentially use geth to create some expected output formats and do string comparison between your generated output and the expected output.
|
Noted. Thank you |
@Wodann is it sufficient to use geth in dev mode to generate the test cases? |
Which issue does this PR close?
Changes
RpcDebugTraceResult
andRpcDebugTraceLogItem
that conform to expected structure.How was this tested?