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

Bug: ExecutionResources specs mismatch (v7 and v8) #2497

Closed
wants to merge 7 commits into from

Conversation

hudem1
Copy link
Contributor

@hudem1 hudem1 commented Feb 16, 2025

No refactor after all but bug solving.

DRAFT: I realized I also need to update simulateTransaction where ExecutionResources is also used.

Created another PR with a different approach as agreed.

Context

// in vm pkg

type TransactionTrace struct {
	Type                  TransactionType     
	ValidateInvocation    *FunctionInvocation 
	ExecuteInvocation     *ExecuteInvocation  
	FeeTransferInvocation *FunctionInvocation 
	ConstructorInvocation *FunctionInvocation 
	FunctionInvocation    *FunctionInvocation 
	StateDiff             *StateDiff          
	ExecutionResources    *ExecutionResources <-- What i call root level ExecutionResources
}

type FunctionInvocation struct {
	ContractAddress    felt.Felt                
	EntryPointSelector *felt.Felt               
	Calldata           []felt.Felt              
	CallerAddress      felt.Felt                
	ClassHash          *felt.Felt               
	EntryPointType     string                   
	CallType           string                   
	Result             []felt.Felt              
	Calls              []FunctionInvocation     
	Events             []OrderedEvent           
	Messages           []OrderedL2toL1Message   
	ExecutionResources *ExecutionResources <-- What i call inner call ExecutionResources
}

Problem

In this PR, instead of refactoring, I actually corrected the returned trace according to the specs, more specifically, the ExecutionResources struct in a trace:

  1. v7:
  • ExecutionResources at the trace root level should contain: ComputationResources + DataAvailability --> no matter the type of tx, the TRANSACTION_TRACE'sExecutionResources field always lead to ExecutionResources
  • ExecutionResources in trace's inner calls should contain: ComputationResources
  1. v8:
  • ExecutionResources at the trace root level should contain: L1Gas, L1DataGas, L2Gas --> no matter the type of tx, the TRANSACTION_TRACE's ExecutionResources field always lead to ExecutionResources
  • ExecutionResources in trace's inner calls should contain: L1Gas, L2Gas

I think the reason for the different fields in inner and root-level ExecutionResources within a same rpc version, is: for example for v8, it's maybe because L1DataGas is for putting the tx's data on L1. And this applies to the whole tx, and cant be applied to part of it. Whereas, in a tx, some inner calls can have specific L1Gas (L2ToL1Msg) and L2Gas. Same for reasoning for the differences in v7's inner and root-level ExecutionResources.

Solution

To adjust the ExecutionResources field correctly, instead of re-creating an ExecutionResources type for trace root-level as well as for inner calls for v7 and v8 (which would also force me to re-create the whole TransactionTrace type just to refer to the custom v7 and v8 ExecutionResources inner and root-level types) and then marshal for specific pkgs, I kept the original vm.TransactionTrace, transform some fields into pointers + added omitempty on those fields. To make it work, I added some cleaning functions for traces executed and retrieved by the VM to clean the ExecutionResources.

Btw for those cleaning fcts, I used an iterative approach because even though recursion gives the same time & space complexities and looks a bit easier to understand imo, the iterative approach allows to avoid any stack overflow (even though probably the inner calls of traces will not go deep enough as to create a stack overflow, but just in case). That being said, appending to my custom slice stack might add a bit of overhead for memory allocation compared to recursion. A good compromise would have been using tail recursion but it seems like Go's compiler doesn't do tail call opti unfortunately :(

@hudem1 hudem1 mentioned this pull request Feb 16, 2025
23 tasks
Copy link

codecov bot commented Feb 16, 2025

Codecov Report

Attention: Patch coverage is 83.75000% with 13 lines in your changes missing coverage. Please review.

Project coverage is 73.53%. Comparing base (0e9e53f) to head (6cb59c1).
Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
rpc/v8/trace.go 83.60% 8 Missing and 2 partials ⚠️
rpc/v6/simulation.go 0.00% 1 Missing ⚠️
rpc/v6/trace.go 50.00% 1 Missing ⚠️
rpc/v7/simulation.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2497      +/-   ##
==========================================
+ Coverage   73.49%   73.53%   +0.04%     
==========================================
  Files         136      136              
  Lines       16657    16650       -7     
==========================================
+ Hits        12242    12244       +2     
+ Misses       3547     3540       -7     
+ Partials      868      866       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hudem1 hudem1 marked this pull request as draft February 16, 2025 16:43
@hudem1 hudem1 marked this pull request as ready for review February 16, 2025 17:06
@hudem1 hudem1 changed the title Refactor: Reuse v7 handler for v8 Refactor: Reuse v7 starknet_traceTransaction handler for v8 Feb 16, 2025
Copy link
Contributor

@AnkushinDaniil AnkushinDaniil left a comment

Choose a reason for hiding this comment

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

We actually have to have

		L1Gas:     l1Gas,
		L1DataGas: l1DataGas,
		L2Gas:     l2Gas,

in v8 and ComputationResources in v7. I described this issue in #2430

@hudem1 hudem1 added RPC JSON RPC API Refactor labels Feb 17, 2025
@hudem1 hudem1 marked this pull request as draft February 17, 2025 15:18
@hudem1 hudem1 marked this pull request as ready for review February 19, 2025 21:57
@hudem1 hudem1 added the High priority Needs to be solved this week or the next label Feb 24, 2025
@hudem1 hudem1 changed the title Refactor: Reuse v7 starknet_traceTransaction handler for v8 Bug: ExecutionResources specs mismatch (v7 and v8) Feb 24, 2025
@hudem1 hudem1 added Bug Something isn't working or security issue and removed Refactor labels Feb 24, 2025
@hudem1 hudem1 marked this pull request as draft February 24, 2025 13:58

ComputationResources
*ComputationResources
Copy link
Contributor

Choose a reason for hiding this comment

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

Why should be this one be a pointer?

Copy link
Contributor Author

@hudem1 hudem1 Feb 24, 2025

Choose a reason for hiding this comment

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

Because, in v8, we don't want to have ComputationResources fields inside ExecutionResources. So, i needed to set it to its zero-value so that it doesn't get marshalled after, which means:

1- It creates a whole ComputationResources instance (14x uint64) just to finally get ignored when marshalled. So, I thought it was not the most efficient.

2- ComputationResources contains the field Step that doesn't have omitempty. So, without the pointer to the whole struct, it would still end up getting marshalled. I could've added omitempty on the Step field but as I thought about the efficiency issue that I mentioned in 1-, I thought might be better putting the ComputationResources as a pointer.

Comment on lines +250 to +252
L1Gas *uint64 `json:"l1_gas,omitempty"`
L1DataGas *uint64 `json:"l1_data_gas,omitempty"`
L2Gas *uint64 `json:"l2_gas,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Are all of this optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mmh the thing is we reuse the TransactionTrace from the VM pkg for all rpc versions. So, we don't want those fields for v6 and v7, but we want them for v8. That's why, i had to put them as optional, and make sure they are set in v8

rpc/v8/trace.go Outdated
if fnInvocation.ExecutionResources != nil {
// Still return them if they are nil
if fnInvocation.ExecutionResources.L1Gas == nil {
fnInvocation.ExecutionResources.L1Gas = utils.Ptr(uint64(0))
Copy link
Contributor

Choose a reason for hiding this comment

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

Use new to create an object and get a reference. Eg:

x := new(uint64)

Comment on lines +356 to +360
for len(stack) > 0 {
// Pop a FunctionInvocation from the stack
n := len(stack) - 1
fnInvocation := stack[n]
stack = stack[:n]
Copy link
Contributor

Choose a reason for hiding this comment

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

Re-slicing can be very expensive. I think in this case is better just to move the pointer to the left

Copy link
Contributor Author

@hudem1 hudem1 Feb 24, 2025

Choose a reason for hiding this comment

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

yes i thought about that but its slicing to remove the last element on the slice, so it should be O(1)

rpc/v8/trace.go Outdated
Comment on lines 337 to 344
pushFnInvocationIfNotNil := func(fn *vm.FunctionInvocation) {
if fn != nil {
stack = append(stack, fn)
}
}

pushFnInvocationIfNotNil(trace.FeeTransferInvocation)
pushFnInvocationIfNotNil(trace.ValidateInvocation)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think adding a function to avoid writing an if three times might be a bit of an over stretch. I think it might be a bit more readable if just remove the function definition and write the ifs.

Also, I believe it is not performant friendly and there is little optimizations that compiler can due to the existence of a closure in the definition

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh okay good to know!
Done!

// Clean trace's inner execution resources to return only the expected fields
func cleanTraceInnerExecutionResources(trace *vm.TransactionTrace) {
// Stack to process FunctionInvocations iteratively (dfs)
stack := []*vm.FunctionInvocation{}
Copy link
Contributor

Choose a reason for hiding this comment

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

You know this variable is going to have at maximum size 3. Create the array with default capacity of 3 (using make).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, it might have more than 3 because when doing dfs, i push the inner/nested FunctionInvocation (to clean the nested ExecutionResources)

Copy link
Contributor

@rodrigo-pino rodrigo-pino left a comment

Choose a reason for hiding this comment

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

I was reading the comment on the PR and this PR is affecting the very first thing we wanted to avoid. We are changing structs across diferent packages to match different specs (or am I missing something).

It's ok to have a vX and a vY structs that are 99% equal duplicated. The whole goal of the refactor was to make it simple to support at the cost of duplicated code.

@hudem1
Copy link
Contributor Author

hudem1 commented Feb 24, 2025

I was reading the comment on the PR and this PR is affecting the very first thing we wanted to avoid. We are changing structs across diferent packages to match different specs (or am I missing something).

It's ok to have a vX and a vY structs that are 99% equal duplicated. The whole goal of the refactor was to make it simple to support at the cost of duplicated code.

Indeed, the main issue seems to be that the TransactionTrace type from the VM pkg is used across all 3 rpc versions. So, the current code is trying to use the same type while still returning different versions of it (because of the ExecutionResources differences b/w rpc versions).

To solve that bug/issue, we could duplicate the TransactionTrace type for the 3 rpc versions and have them use their own ExecutionResources.

@rodrigo-pino
Copy link
Contributor

Superceded by #2563

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working or security issue High priority Needs to be solved this week or the next RPC JSON RPC API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants