Skip to content

Commit

Permalink
fix(rpc): TransactionTrace matches rpc v8 specs (#2600)
Browse files Browse the repository at this point in the history
* feat(rpcv6): Use rpcv6.TransactionTrace instead of vm.TransactionTrace

* fix: Added tests for coverage and fix lint

* fix: Use & instead of utils.Ptr()

* Revert "fix: Use & instead of utils.Ptr()"

This reverts commit 428b1ea.

* fix: Use & instead of utils.Ptr when it makes a difference

* fix: StateDiff field is mandatory for traces

* fix: Actually trace.StateDiff is not mandatory!

* fix: Add validation on transaction trace

* fix: TransactionTrace.Events matches specs

* fix: TransactionTrace.Messages matches specs

* test: Add test for adapting vm l2 to l1 messages

* fix: Use Sepolia instead of Goerli Integration for tests

* fix: All TransactionTrace fields are mandatory

* feat(rpc): TransactionTrace matches rpc v7 specs

* fix: Add required fields marshalling test

* fix: Add StateDiff nil check - Remove utils.Ptr where possible

* fix: Set trace FunctionInvocation only for specific trace type

* fix: Improve some v6 tests

* fix: lint

* feat: Bring back old Goerli Integration test (and keep new Sepolia test)

* fix: Use & instead of utils.HeapPtr when better

* fix: Make ExecutionResources.DataAvailability mandatory

* feat(rpc): TransactionTrace matches rpc v8 specs

* feat: Add isReverted field from VM and feeder

* fix: Replace utils.Ptr by utils.HeapPtr

* fix: Set trace FunctionInvocation only for specific trace type

* feat: Bring back old Goerli Integration test (and keep new Sepolia test)

* fix: Reverted INVOKE tx returns a non-nil ExecuteInvocation in trace

* fix: lint

* fix: Reverted INVOKE tx returns a non-nil ExecuteInvoc in v8 too

* fix(PR reviews): fct inlining, divide tests, rename var

---------

Co-authored-by: Rodrigo <[email protected]>
  • Loading branch information
hudem1 and rodrigo-pino authored Mar 7, 2025
1 parent 73d8f7c commit 19cf1c4
Show file tree
Hide file tree
Showing 14 changed files with 1,245 additions and 430 deletions.
8 changes: 2 additions & 6 deletions rpc/v6/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,7 @@ type OrderedL2toL1Message struct {
// It follows the specification defined here:
// https://github.com/starkware-libs/starknet-specs/blob/1ae810e0137cc5d175ace4554892a4f43052be56/api/starknet_trace_api_openrpc.json#L11
func (h *Handler) TraceTransaction(ctx context.Context, hash felt.Felt) (*TransactionTrace, *jsonrpc.Error) {
return h.traceTransaction(ctx, &hash)
}

func (h *Handler) traceTransaction(ctx context.Context, hash *felt.Felt) (*TransactionTrace, *jsonrpc.Error) {
_, blockHash, _, err := h.bcReader.Receipt(hash)
_, blockHash, _, err := h.bcReader.Receipt(&hash)
if err != nil {
return nil, rpccore.ErrTxnHashNotFound
}
Expand All @@ -114,7 +110,7 @@ func (h *Handler) traceTransaction(ctx context.Context, hash *felt.Felt) (*Trans
}

txIndex := slices.IndexFunc(block.Transactions, func(tx core.Transaction) bool {
return tx.Hash().Equal(hash)
return tx.Hash().Equal(&hash)
})
if txIndex == -1 {
return nil, rpccore.ErrTxnHashNotFound
Expand Down
49 changes: 37 additions & 12 deletions rpc/v6/trace_test.go

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions rpc/v7/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ func (t *TransactionTrace) allInvocations() []*rpcv6.FunctionInvocation {
// It follows the specification defined here:
// https://github.com/starkware-libs/starknet-specs/blob/1ae810e0137cc5d175ace4554892a4f43052be56/api/starknet_trace_api_openrpc.json#L11
func (h *Handler) TraceTransaction(ctx context.Context, hash felt.Felt) (*TransactionTrace, http.Header, *jsonrpc.Error) {
return h.traceTransaction(ctx, &hash)
}

func (h *Handler) traceTransaction(ctx context.Context, hash *felt.Felt) (*TransactionTrace, http.Header, *jsonrpc.Error) {
_, blockHash, _, err := h.bcReader.Receipt(hash)
_, blockHash, _, err := h.bcReader.Receipt(&hash)
httpHeader := http.Header{}
httpHeader.Set(ExecutionStepsHeader, "0")

Expand All @@ -112,7 +108,7 @@ func (h *Handler) traceTransaction(ctx context.Context, hash *felt.Felt) (*Trans
}

txIndex := slices.IndexFunc(block.Transactions, func(tx core.Transaction) bool {
return tx.Hash().Equal(hash)
return tx.Hash().Equal(&hash)
})
if txIndex == -1 {
return nil, httpHeader, rpccore.ErrTxnHashNotFound
Expand Down
51 changes: 38 additions & 13 deletions rpc/v7/trace_test.go

Large diffs are not rendered by default.

271 changes: 271 additions & 0 deletions rpc/v8/adapters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
package rpcv8

import (
"errors"

"github.com/NethermindEth/juno/core/felt"
rpcv6 "github.com/NethermindEth/juno/rpc/v6"
"github.com/NethermindEth/juno/starknet"
"github.com/NethermindEth/juno/utils"
"github.com/NethermindEth/juno/vm"
)

/****************************************************
VM Adapters
*****************************************************/

func AdaptVMTransactionTrace(trace *vm.TransactionTrace) TransactionTrace {
var validateInvocation *FunctionInvocation
if trace.ValidateInvocation != nil && trace.Type != vm.TxnL1Handler {
validateInvocation = utils.HeapPtr(adaptVMFunctionInvocation(trace.ValidateInvocation))
}

var feeTransferInvocation *FunctionInvocation
if trace.FeeTransferInvocation != nil && trace.Type != vm.TxnL1Handler {
feeTransferInvocation = utils.HeapPtr(adaptVMFunctionInvocation(trace.FeeTransferInvocation))
}

var constructorInvocation *FunctionInvocation
var executeInvocation *ExecuteInvocation
var functionInvocation *FunctionInvocation

switch trace.Type {
case vm.TxnDeployAccount, vm.TxnDeploy:
if trace.ConstructorInvocation != nil {
constructorInvocation = utils.HeapPtr(adaptVMFunctionInvocation(trace.ConstructorInvocation))
}
case vm.TxnInvoke:
if trace.ExecuteInvocation != nil {
executeInvocation = utils.HeapPtr(adaptVMExecuteInvocation(trace.ExecuteInvocation))
}
case vm.TxnL1Handler:
if trace.FunctionInvocation != nil {
functionInvocation = utils.HeapPtr(adaptVMFunctionInvocation(trace.FunctionInvocation))
}
}

var resources *ExecutionResources
if trace.ExecutionResources != nil {
resources = utils.HeapPtr(adaptVMExecutionResources(trace.ExecutionResources))
}

var stateDiff *rpcv6.StateDiff
if trace.StateDiff != nil {
stateDiff = utils.HeapPtr(rpcv6.AdaptVMStateDiff(trace.StateDiff))
}

return TransactionTrace{
Type: TransactionType(trace.Type),
ValidateInvocation: validateInvocation,
ExecuteInvocation: executeInvocation,
FeeTransferInvocation: feeTransferInvocation,
ConstructorInvocation: constructorInvocation,
FunctionInvocation: functionInvocation,
StateDiff: stateDiff,
ExecutionResources: resources,
}
}

func adaptVMExecuteInvocation(vmFnInvocation *vm.ExecuteInvocation) ExecuteInvocation {
var functionInvocation *FunctionInvocation
if vmFnInvocation.FunctionInvocation != nil {
functionInvocation = utils.HeapPtr(adaptVMFunctionInvocation(vmFnInvocation.FunctionInvocation))
}

return ExecuteInvocation{
RevertReason: vmFnInvocation.RevertReason,
FunctionInvocation: functionInvocation,
}
}

func adaptVMFunctionInvocation(vmFnInvocation *vm.FunctionInvocation) FunctionInvocation {
// Adapt inner calls
adaptedCalls := make([]FunctionInvocation, len(vmFnInvocation.Calls))
for index := range vmFnInvocation.Calls {
adaptedCalls[index] = adaptVMFunctionInvocation(&vmFnInvocation.Calls[index])
}

// Adapt events
adaptedEvents := make([]rpcv6.OrderedEvent, len(vmFnInvocation.Events))
for index := range vmFnInvocation.Events {
vmEvent := &vmFnInvocation.Events[index]

adaptedEvents[index] = rpcv6.OrderedEvent{
Order: vmEvent.Order,
Keys: vmEvent.Keys,
Data: vmEvent.Data,
}
}

// Adapt messages
adaptedMessages := make([]rpcv6.OrderedL2toL1Message, len(vmFnInvocation.Messages))
for index := range vmFnInvocation.Messages {
vmMessage := &vmFnInvocation.Messages[index]

toAddr, _ := new(felt.Felt).SetString(vmMessage.To)

adaptedMessages[index] = rpcv6.OrderedL2toL1Message{
Order: vmMessage.Order,
From: vmMessage.From,
To: toAddr,
Payload: vmMessage.Payload,
}
}

// Adapt execution resources
var adaptedResources *InnerExecutionResources
if r := vmFnInvocation.ExecutionResources; r != nil {
adaptedResources = &InnerExecutionResources{
L1Gas: r.L1Gas,
L2Gas: r.L2Gas,
}
}

return FunctionInvocation{
ContractAddress: vmFnInvocation.ContractAddress,
EntryPointSelector: vmFnInvocation.EntryPointSelector,
Calldata: vmFnInvocation.Calldata,
CallerAddress: vmFnInvocation.CallerAddress,
ClassHash: vmFnInvocation.ClassHash,
EntryPointType: vmFnInvocation.EntryPointType,
CallType: vmFnInvocation.CallType,
Result: vmFnInvocation.Result,
Calls: adaptedCalls,
Events: adaptedEvents,
Messages: adaptedMessages,
ExecutionResources: adaptedResources,
IsReverted: vmFnInvocation.IsReverted,
}
}

func adaptVMExecutionResources(r *vm.ExecutionResources) ExecutionResources {
return ExecutionResources{
InnerExecutionResources: InnerExecutionResources{
L1Gas: r.L1Gas,
L2Gas: r.L2Gas,
},
L1DataGas: r.L1DataGas,
}
}

/****************************************************
Feeder Adapters
*****************************************************/

func AdaptFeederBlockTrace(block *BlockWithTxs, blockTrace *starknet.BlockTrace) ([]TracedBlockTransaction, error) {
if blockTrace == nil {
return nil, nil
}

if len(block.Transactions) != len(blockTrace.Traces) {
return nil, errors.New("mismatched number of txs and traces")
}

// Adapt every feeder block trace to rpc v8 trace
adaptedTraces := make([]TracedBlockTransaction, len(blockTrace.Traces))
for index := range blockTrace.Traces {
feederTrace := &blockTrace.Traces[index]

trace := TransactionTrace{
Type: block.Transactions[index].Type,
}

if feederTrace.FeeTransferInvocation != nil && trace.Type != TxnL1Handler {
trace.FeeTransferInvocation = utils.HeapPtr(adaptFeederFunctionInvocation(feederTrace.FeeTransferInvocation))
}

if feederTrace.ValidateInvocation != nil && trace.Type != TxnL1Handler {
trace.ValidateInvocation = utils.HeapPtr(adaptFeederFunctionInvocation(feederTrace.ValidateInvocation))
}

var fnInvocation *FunctionInvocation
if fct := feederTrace.FunctionInvocation; fct != nil {
fnInvocation = utils.HeapPtr(adaptFeederFunctionInvocation(fct))
}

switch trace.Type {
case TxnDeploy, TxnDeployAccount:
trace.ConstructorInvocation = fnInvocation
case TxnInvoke:
trace.ExecuteInvocation = new(ExecuteInvocation)
if feederTrace.RevertError != "" {
trace.ExecuteInvocation.RevertReason = feederTrace.RevertError
} else {
trace.ExecuteInvocation.FunctionInvocation = fnInvocation
}
case TxnL1Handler:
trace.FunctionInvocation = fnInvocation
}

adaptedTraces[index] = TracedBlockTransaction{
TransactionHash: &feederTrace.TransactionHash,
TraceRoot: &trace,
}
}

return adaptedTraces, nil
}

func adaptFeederFunctionInvocation(snFnInvocation *starknet.FunctionInvocation) FunctionInvocation {
// Adapt inner calls
adaptedCalls := make([]FunctionInvocation, len(snFnInvocation.InternalCalls))
for index := range snFnInvocation.InternalCalls {
adaptedCalls[index] = adaptFeederFunctionInvocation(&snFnInvocation.InternalCalls[index])
}

// Adapt events
adaptedEvents := make([]rpcv6.OrderedEvent, len(snFnInvocation.Events))
for index := range snFnInvocation.Events {
snEvent := &snFnInvocation.Events[index]

adaptedEvents[index] = rpcv6.OrderedEvent{
Order: snEvent.Order,
Keys: utils.Map(snEvent.Keys, utils.HeapPtr[felt.Felt]),
Data: utils.Map(snEvent.Data, utils.HeapPtr[felt.Felt]),
}
}

// Adapt messages
adaptedMessages := make([]rpcv6.OrderedL2toL1Message, len(snFnInvocation.Messages))
for index := range snFnInvocation.Messages {
snMessage := &snFnInvocation.Messages[index]

toAddr, _ := new(felt.Felt).SetString(snMessage.ToAddr)

adaptedMessages[index] = rpcv6.OrderedL2toL1Message{
Order: snMessage.Order,
From: &snFnInvocation.ContractAddress,
To: toAddr,
Payload: utils.Map(snMessage.Payload, utils.HeapPtr[felt.Felt]),
}
}

return FunctionInvocation{
ContractAddress: snFnInvocation.ContractAddress,
EntryPointSelector: snFnInvocation.Selector,
Calldata: snFnInvocation.Calldata,
CallerAddress: snFnInvocation.CallerAddress,
ClassHash: snFnInvocation.ClassHash,
EntryPointType: snFnInvocation.EntryPointType,
CallType: snFnInvocation.CallType,
Result: snFnInvocation.Result,
Calls: adaptedCalls,
Events: adaptedEvents,
Messages: adaptedMessages,
ExecutionResources: utils.HeapPtr(adaptFeederExecutionResources(&snFnInvocation.ExecutionResources)),
IsReverted: snFnInvocation.Failed,
}
}

func adaptFeederExecutionResources(resources *starknet.ExecutionResources) InnerExecutionResources {
var l1Gas, l2Gas uint64
if tgs := resources.TotalGasConsumed; tgs != nil {
l1Gas = tgs.L1Gas
l2Gas = tgs.L2Gas
}

return InnerExecutionResources{
L1Gas: l1Gas,
L2Gas: l2Gas,
}
}
35 changes: 19 additions & 16 deletions rpc/v8/simulation.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ func (s *SimulationFlag) UnmarshalJSON(bytes []byte) (err error) {
}

type SimulatedTransaction struct {
TransactionTrace *vm.TransactionTrace `json:"transaction_trace,omitempty"`
FeeEstimation FeeEstimate `json:"fee_estimation,omitempty"`
TransactionTrace *TransactionTrace `json:"transaction_trace,omitempty"`
FeeEstimation FeeEstimate `json:"fee_estimation,omitempty"`
}

type TracedBlockTransaction struct {
TraceRoot *vm.TransactionTrace `json:"trace_root,omitempty"`
TransactionHash *felt.Felt `json:"transaction_hash,omitempty"`
TraceRoot *TransactionTrace `json:"trace_root,omitempty"`
TransactionHash *felt.Felt `json:"transaction_hash,omitempty"`
}

/****************************************************
Expand Down Expand Up @@ -112,7 +112,7 @@ func (h *Handler) simulateTransactions(id BlockID, transactions []BroadcastedTra
func prepareTransactions(transactions []BroadcastedTransaction, network *utils.Network) (
[]core.Transaction, []core.Class, []*felt.Felt, *jsonrpc.Error,
) {
txns := make([]core.Transaction, 0, len(transactions))
txns := make([]core.Transaction, len(transactions))
var classes []core.Class
paidFeesOnL1 := make([]*felt.Felt, 0)

Expand All @@ -126,7 +126,7 @@ func prepareTransactions(transactions []BroadcastedTransaction, network *utils.N
paidFeesOnL1 = append(paidFeesOnL1, paidFeeOnL1)
}

txns = append(txns, txn)
txns[idx] = txn
if declaredClass != nil {
classes = append(classes, declaredClass)
}
Expand All @@ -153,6 +153,7 @@ func createSimulatedTransactions(
traces := executionResults.Traces
gasConsumed := executionResults.GasConsumed
daGas := executionResults.DataAvailability

if len(overallFees) != len(traces) || len(overallFees) != len(gasConsumed) ||
len(overallFees) != len(daGas) || len(overallFees) != len(txns) {
return nil, fmt.Errorf("inconsistent lengths: %d overall fees, %d traces, %d gas consumed, %d data availability, %d txns",
Expand All @@ -177,18 +178,19 @@ func createSimulatedTransactions(

simulatedTransactions := make([]SimulatedTransaction, len(overallFees))
for i, overallFee := range overallFees {
trace := traces[i]
traces[i].ExecutionResources = &vm.ExecutionResources{
L1Gas: gasConsumed[i].L1Gas,
L1DataGas: gasConsumed[i].L1DataGas,
L2Gas: gasConsumed[i].L2Gas,
ComputationResources: trace.TotalComputationResources(),
DataAvailability: &vm.DataAvailability{
L1Gas: daGas[i].L1Gas,
L1DataGas: daGas[i].L1DataGas,
// Adapt transaction trace to rpc v8 trace
trace := utils.HeapPtr(AdaptVMTransactionTrace(&traces[i]))

// Add root level execution resources
trace.ExecutionResources = &ExecutionResources{
InnerExecutionResources: InnerExecutionResources{
L1Gas: gasConsumed[i].L1Gas,
L2Gas: gasConsumed[i].L2Gas,
},
L1DataGas: gasConsumed[i].L1DataGas,
}

// Compute data for FeeEstimate
var l1GasPrice, l2GasPrice, l1DataGasPrice *felt.Felt
feeUnit := feeUnit(txns[i])
switch feeUnit {
Expand All @@ -202,8 +204,9 @@ func createSimulatedTransactions(
l1DataGasPrice = l1DataGasPriceStrk
}

// Append simulated transaction (trace + fee estimate)
simulatedTransactions[i] = SimulatedTransaction{
TransactionTrace: &traces[i],
TransactionTrace: trace,
FeeEstimation: FeeEstimate{
L1GasConsumed: new(felt.Felt).SetUint64(gasConsumed[i].L1Gas),
L1GasPrice: l1GasPrice,
Expand Down
Loading

0 comments on commit 19cf1c4

Please sign in to comment.