diff --git a/builder/executor.go b/builder/executor.go index 4abc6f2ae7..1a7f46d581 100644 --- a/builder/executor.go +++ b/builder/executor.go @@ -80,7 +80,7 @@ func (e *executor) RunTxns(state *BuildState, txns []mempool.BroadcastedTransact } // Execute the transaction - vmResults, err := e.vm.Execute( + vmResults, err := e.vm.BuildBlock( coreTxns, declaredClasses, paidFeesOnL1, @@ -89,13 +89,10 @@ func (e *executor) RunTxns(state *BuildState, txns []mempool.BroadcastedTransact BlockHashToBeRevealed: state.RevealedBlockHash, }, stateWriter, - e.disableFees, - e.skipValidate, - false, - true, - false, - false, - false, + vm.BuildBlockOptions{ + SkipChargeFee: e.disableFees, + SkipValidate: e.skipValidate, + }, ) if err != nil { return err diff --git a/genesis/genesis.go b/genesis/genesis.go index e478745568..2e0835d3e2 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -359,8 +359,17 @@ func executeTransactions( } blockInfo := vm.BlockInfo{Header: &genesisHeader} - executionResults, err := v.Execute(coreTxns, nil, []*felt.Felt{new(felt.Felt).SetUint64(1)}, - &blockInfo, genesisState, true, true, true, true, false, false, false) + executionResults, err := v.BuildBlock( + coreTxns, + nil, + []*felt.Felt{felt.NewFromUint64[felt.Felt](1)}, + &blockInfo, + genesisState, + vm.BuildBlockOptions{ + SkipChargeFee: true, + SkipValidate: true, + ErrOnRevert: true, + }) if err != nil { return fmt.Errorf("execute transactions: %v", err) } diff --git a/mocks/mock_vm.go b/mocks/mock_vm.go index 3e27bc03d7..0ac2276014 100644 --- a/mocks/mock_vm.go +++ b/mocks/mock_vm.go @@ -42,6 +42,21 @@ func (m *MockVM) EXPECT() *MockVMMockRecorder { return m.recorder } +// BuildBlock mocks base method. +func (m *MockVM) BuildBlock(txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, opts vm.BuildBlockOptions) (vm.ExecutionResults, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildBlock", txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) + ret0, _ := ret[0].(vm.ExecutionResults) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildBlock indicates an expected call of BuildBlock. +func (mr *MockVMMockRecorder) BuildBlock(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildBlock", reflect.TypeOf((*MockVM)(nil).BuildBlock), txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) +} + // Call mocks base method. func (m *MockVM) Call(callInfo *vm.CallInfo, blockInfo *vm.BlockInfo, state core.StateReader, maxSteps, maxGas uint64, structuredErrStack, returnStateDiff bool) (vm.CallResult, error) { m.ctrl.T.Helper() @@ -57,17 +72,47 @@ func (mr *MockVMMockRecorder) Call(callInfo, blockInfo, state, maxSteps, maxGas, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockVM)(nil).Call), callInfo, blockInfo, state, maxSteps, maxGas, structuredErrStack, returnStateDiff) } -// Execute mocks base method. -func (m *MockVM) Execute(txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, skipChargeFee, skipValidate, errOnRevert, errStack, allowBinarySearch, isEstimateFee, returnInitialReads bool) (vm.ExecutionResults, error) { +// EstimateFee mocks base method. +func (m *MockVM) EstimateFee(txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, opts vm.EstimateFeeOptions) (vm.ExecutionResults, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EstimateFee", txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) + ret0, _ := ret[0].(vm.ExecutionResults) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EstimateFee indicates an expected call of EstimateFee. +func (mr *MockVMMockRecorder) EstimateFee(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateFee", reflect.TypeOf((*MockVM)(nil).EstimateFee), txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) +} + +// Simulate mocks base method. +func (m *MockVM) Simulate(txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, opts vm.SimulateOptions) (vm.ExecutionResults, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Simulate", txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) + ret0, _ := ret[0].(vm.ExecutionResults) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Simulate indicates an expected call of Simulate. +func (mr *MockVMMockRecorder) Simulate(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Simulate", reflect.TypeOf((*MockVM)(nil).Simulate), txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) +} + +// Trace mocks base method. +func (m *MockVM) Trace(txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, opts vm.TraceOptions) (vm.ExecutionResults, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Execute", txns, declaredClasses, paidFeesOnL1, blockInfo, state, skipChargeFee, skipValidate, errOnRevert, errStack, allowBinarySearch, isEstimateFee, returnInitialReads) + ret := m.ctrl.Call(m, "Trace", txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) ret0, _ := ret[0].(vm.ExecutionResults) ret1, _ := ret[1].(error) return ret0, ret1 } -// Execute indicates an expected call of Execute. -func (mr *MockVMMockRecorder) Execute(txns, declaredClasses, paidFeesOnL1, blockInfo, state, skipChargeFee, skipValidate, errOnRevert, errStack, allowBinarySearch, isEstimateFee, returnInitialReads any) *gomock.Call { +// Trace indicates an expected call of Trace. +func (mr *MockVMMockRecorder) Trace(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockVM)(nil).Execute), txns, declaredClasses, paidFeesOnL1, blockInfo, state, skipChargeFee, skipValidate, errOnRevert, errStack, allowBinarySearch, isEstimateFee, returnInitialReads) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trace", reflect.TypeOf((*MockVM)(nil).Trace), txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) } diff --git a/node/throttled_vm.go b/node/throttled_vm.go index 47d1332efd..39d54d6590 100644 --- a/node/throttled_vm.go +++ b/node/throttled_vm.go @@ -43,37 +43,65 @@ func (tvm *ThrottledVM) Call( }) } -func (tvm *ThrottledVM) Execute( +func (tvm *ThrottledVM) runExec( + fn func(inner vm.VM) (vm.ExecutionResults, error), +) (vm.ExecutionResults, error) { + var result vm.ExecutionResults + return result, tvm.Do(func(inner *vm.VM) error { + var err error + result, err = fn(*inner) + return err + }) +} + +func (tvm *ThrottledVM) Simulate( txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, - skipChargeFee, - skipValidate, - errOnRevert, - errStack, - allowBinarySearch bool, - isEstimateFee bool, - returnInitialReads bool, + opts vm.SimulateOptions, ) (vm.ExecutionResults, error) { - var executionResult vm.ExecutionResults - return executionResult, tvm.Do(func(vm *vm.VM) error { - var err error - executionResult, err = (*vm).Execute( - txns, - declaredClasses, - paidFeesOnL1, - blockInfo, - state, - skipChargeFee, - skipValidate, - errOnRevert, - errStack, - allowBinarySearch, - isEstimateFee, - returnInitialReads, - ) - return err + return tvm.runExec(func(inner vm.VM) (vm.ExecutionResults, error) { + return inner.Simulate(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) + }) +} + +func (tvm *ThrottledVM) EstimateFee( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *vm.BlockInfo, + state core.StateReader, + opts vm.EstimateFeeOptions, +) (vm.ExecutionResults, error) { + return tvm.runExec(func(inner vm.VM) (vm.ExecutionResults, error) { + return inner.EstimateFee(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) + }) +} + +func (tvm *ThrottledVM) Trace( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *vm.BlockInfo, + state core.StateReader, + opts vm.TraceOptions, +) (vm.ExecutionResults, error) { + return tvm.runExec(func(inner vm.VM) (vm.ExecutionResults, error) { + return inner.Trace(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) + }) +} + +func (tvm *ThrottledVM) BuildBlock( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *vm.BlockInfo, + state core.StateReader, + opts vm.BuildBlockOptions, +) (vm.ExecutionResults, error) { + return tvm.runExec(func(inner vm.VM) (vm.ExecutionResults, error) { + return inner.BuildBlock(txns, declaredClasses, paidFeesOnL1, blockInfo, state, opts) }) } diff --git a/rpc/v10/estimate_fee.go b/rpc/v10/estimate_fee.go index 747be2ae2b..724f30e0ae 100644 --- a/rpc/v10/estimate_fee.go +++ b/rpc/v10/estimate_fee.go @@ -34,7 +34,7 @@ func (h *Handler) EstimateFee( estimateFlags []EstimateFlag, id *BlockID, ) ([]FeeEstimate, http.Header, *jsonrpc.Error) { - simulationFlags := make([]SimulationFlag, 0, len(estimateFlags)+1) + simulationFlags := make([]SimulationFlag, 0, len(estimateFlags)) for _, flag := range estimateFlags { simulationFlag, err := flag.ToSimulationFlag() if err != nil { @@ -47,7 +47,7 @@ func (h *Handler) EstimateFee( ctx, id, broadcastedTxns.Data, - append(simulationFlags, SkipFeeChargeFlag), + simulationFlags, true, true, ) diff --git a/rpc/v10/estimate_fee_test.go b/rpc/v10/estimate_fee_test.go index 4378f98508..fe4170e6d2 100644 --- a/rpc/v10/estimate_fee_test.go +++ b/rpc/v10/estimate_fee_test.go @@ -38,15 +38,13 @@ func TestEstimateFee(t *testing.T) { blockInfo := vm.BlockInfo{Header: &core.Header{}} t.Run("ok with zero values", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - false, - true, true, true, true, false). + vm.EstimateFeeOptions{}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -66,15 +64,15 @@ func TestEstimateFee(t *testing.T) { }) t.Run("ok with zero values, skip validate", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - true, - true, true, true, true, false). + vm.EstimateFeeOptions{ + SkipValidate: true, + }). Return( vm.ExecutionResults{ OverallFees: []*felt.Felt{}, @@ -99,15 +97,15 @@ func TestEstimateFee(t *testing.T) { }) t.Run("transaction execution error", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - true, - true, true, true, true, false). + vm.EstimateFeeOptions{ + SkipValidate: true, + }). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, Cause: json.RawMessage("oops"), diff --git a/rpc/v10/simulation.go b/rpc/v10/simulation.go index f6251aa132..1436a6f374 100644 --- a/rpc/v10/simulation.go +++ b/rpc/v10/simulation.go @@ -221,20 +221,34 @@ func (h *Handler) simulateTransactions( BlockHashToBeRevealed: blockHashToBeRevealed, } - executionResults, err := h.vm.Execute( - txns, - classes, - paidFeesOnL1, - &blockInfo, - state, - skipFeeCharge, - skipValidate, - errOnRevert, - true, - true, - isEstimateFee, - returnInitialReads, - ) + var executionResults vm.ExecutionResults + if isEstimateFee { + executionResults, err = h.vm.EstimateFee( + txns, + classes, + paidFeesOnL1, + &blockInfo, + state, + vm.EstimateFeeOptions{ + SkipValidate: skipValidate, + ReturnInitialReads: returnInitialReads, + }, + ) + } else { + executionResults, err = h.vm.Simulate( + txns, + classes, + paidFeesOnL1, + &blockInfo, + state, + vm.SimulateOptions{ + SkipChargeFee: skipFeeCharge, + SkipValidate: skipValidate, + ErrOnRevert: errOnRevert, + ReturnInitialReads: returnInitialReads, + }, + ) + } if err != nil { return SimulateTransactionsResponse{}, httpHeader, handleExecutionError(err) } diff --git a/rpc/v10/simulation_test.go b/rpc/v10/simulation_test.go index 22a7b216c7..5d42a606f1 100644 --- a/rpc/v10/simulation_test.go +++ b/rpc/v10/simulation_test.go @@ -63,9 +63,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, true, false, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipChargeFee: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -86,9 +86,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -108,9 +108,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, Cause: json.RawMessage("oops"), @@ -130,9 +130,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{&felt.Zero}, DataAvailability: []core.DataAvailability{{L1Gas: 0}, {L1Gas: 0}}, @@ -454,8 +454,8 @@ func TestSimulateTransactionsWithReturnInitialReads(t *testing.T) { }, }} - mockVM.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, - false, false, false, true, true, false, returnInitialReads, + mockVM.EXPECT().Simulate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, + vm.SimulateOptions{ReturnInitialReads: returnInitialReads}, ).Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{&felt.Zero}, DataAvailability: []core.DataAvailability{{L1Gas: 0}}, diff --git a/rpc/v10/trace.go b/rpc/v10/trace.go index 4a591ee667..8d2d5c2d2e 100644 --- a/rpc/v10/trace.go +++ b/rpc/v10/trace.go @@ -163,7 +163,7 @@ func (h *Handler) TraceBlockTransactions( // // - returnInitialReads: Whether to return initial reads in the response func traceTransactionsWithState( - vm vm.VM, + runner vm.VM, transactions []core.Transaction, executionState core.StateReader, classLookupState core.StateReader, @@ -181,19 +181,13 @@ func traceTransactionsWithState( return nil, nil, httpHeader, err } - executionResult, vmErr := vm.Execute( + executionResult, vmErr := runner.Trace( transactions, declaredClasses, paidFeesOnL1, blockInfo, executionState, - false, // skipValidate - false, // skipFeeCharge - false, // skipNonceCharge - true, // allowZeroMaxFee - false, // allowNoSignature - false, // isEstimateFee - returnInitialReads, + vm.TraceOptions{ReturnInitialReads: returnInitialReads}, ) httpHeader.Set(ExecutionStepsHeader, strconv.FormatUint(executionResult.NumSteps, 10)) diff --git a/rpc/v10/trace_test.go b/rpc/v10/trace_test.go index 35600b91ea..94c1ec750a 100644 --- a/rpc/v10/trace_test.go +++ b/rpc/v10/trace_test.go @@ -404,19 +404,13 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false).Return(vm.ExecutionResults{ + vm.TraceOptions{}).Return(vm.ExecutionResults{ OverallFees: overallFee, GasConsumed: gc, Traces: []vm.TransactionTrace{vmTrace}, @@ -481,19 +475,13 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, nil, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false, + vm.TraceOptions{}, ). Return(vm.ExecutionResults{ OverallFees: overallFee, @@ -575,19 +563,13 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false, + vm.TraceOptions{}, ). Return(vm.ExecutionResults{ OverallFees: overallFee, @@ -733,25 +715,20 @@ func TestTraceBlockTransactions(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false).Return(vm.ExecutionResults{ - OverallFees: nil, - DataAvailability: []core.DataAvailability{{}, {}}, - GasConsumed: []core.GasConsumed{{}, {}}, - Traces: []vm.TransactionTrace{vmTrace}, - NumSteps: stepsUsed, - }, nil) + vm.TraceOptions{}). + Return(vm.ExecutionResults{ + OverallFees: nil, + DataAvailability: []core.DataAvailability{{}, {}}, + GasConsumed: []core.GasConsumed{{}, {}}, + Traces: []vm.TransactionTrace{vmTrace}, + NumSteps: stepsUsed, + }, nil) expectedTrace := rpcv10.AdaptVMTransactionTrace(&vmTrace) expectedResult := []rpcv10.TracedBlockTransaction{ @@ -1419,8 +1396,8 @@ func TestTraceBlockTransactionsWithReturnInitialReads(t *testing.T) { returnInitialReads := slices.Contains(test.simulationFlags, rpcv10.ReturnInitialReadsFlag) - mockVM.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, - false, false, false, true, false, false, returnInitialReads, + mockVM.EXPECT().Trace(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, + vm.TraceOptions{ReturnInitialReads: returnInitialReads}, ).Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{&felt.Zero}, DataAvailability: []core.DataAvailability{{L1Gas: 0}}, @@ -1536,12 +1513,12 @@ func TestTraceBlockTransactionsInitialReadsCacheCoherence(t *testing.T) { // First VM call: no initial reads requested. gomock.InOrder( - mockVM.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, - false, false, false, true, false, false, false, + mockVM.EXPECT().Trace(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, + vm.TraceOptions{}, ).Return(execResultWithReads(nil), nil), // Second VM call: flag set, VM produces populated reads. - mockVM.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, - false, false, false, true, false, false, true, + mockVM.EXPECT().Trace(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, + vm.TraceOptions{ReturnInitialReads: true}, ).Return(execResultWithReads(populatedVMReads()), nil), ) @@ -1578,8 +1555,8 @@ func TestTraceBlockTransactionsInitialReadsCacheCoherence(t *testing.T) { mockReader.EXPECT().StateAtBlockHash(block.ParentHash).Return(mockState, nopCloser, nil) mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil) - mockVM.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, - false, false, false, true, false, false, true, + mockVM.EXPECT().Trace(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, + vm.TraceOptions{ReturnInitialReads: true}, ).Return(execResultWithReads(populatedVMReads()), nil) handler := rpcv10.New(mockReader, nil, mockVM, log.NewNopZapLogger()) @@ -1614,8 +1591,8 @@ func TestTraceBlockTransactionsInitialReadsCacheCoherence(t *testing.T) { mockReader.EXPECT().StateAtBlockHash(block.ParentHash).Return(mockState, nopCloser, nil) mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil) - mockVM.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, - false, false, false, true, false, false, true, + mockVM.EXPECT().Trace(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), mockState, + vm.TraceOptions{ReturnInitialReads: true}, ).Return(execResultWithReads(populatedVMReads()), nil) handler := rpcv10.New(mockReader, nil, mockVM, log.NewNopZapLogger()) diff --git a/rpc/v8/estimate_fee.go b/rpc/v8/estimate_fee.go index 0c41dec98f..69f1e8eb6c 100644 --- a/rpc/v8/estimate_fee.go +++ b/rpc/v8/estimate_fee.go @@ -279,7 +279,7 @@ func (h *Handler) EstimateFee( ctx, id, broadcastedTxns.Data, - append(simulationFlags, SkipFeeChargeFlag), + simulationFlags, true, true, ) diff --git a/rpc/v8/estimate_fee_test.go b/rpc/v8/estimate_fee_test.go index 63cff7dc24..5d83c6cadb 100644 --- a/rpc/v8/estimate_fee_test.go +++ b/rpc/v8/estimate_fee_test.go @@ -38,15 +38,13 @@ func TestEstimateFee(t *testing.T) { blockInfo := vm.BlockInfo{Header: &core.Header{}} t.Run("ok with zero values", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - false, - true, true, true, true, false). + vm.EstimateFeeOptions{}). Return( vm.ExecutionResults{ OverallFees: []*felt.Felt{}, @@ -69,15 +67,15 @@ func TestEstimateFee(t *testing.T) { }) t.Run("ok with zero values, skip validate", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - true, - true, true, true, true, false). + vm.EstimateFeeOptions{ + SkipValidate: true, + }). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -97,15 +95,15 @@ func TestEstimateFee(t *testing.T) { }) t.Run("transaction execution error", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - true, - true, true, true, true, false). + vm.EstimateFeeOptions{ + SkipValidate: true, + }). Return( vm.ExecutionResults{}, vm.TransactionExecutionError{ diff --git a/rpc/v8/simulation.go b/rpc/v8/simulation.go index 6e0b8d02fe..d510f1b82d 100644 --- a/rpc/v8/simulation.go +++ b/rpc/v8/simulation.go @@ -120,8 +120,20 @@ func (h *Handler) simulateTransactions( BlockHashToBeRevealed: blockHashToBeRevealed, } - executionResults, err := h.vm.Execute(txns, classes, paidFeesOnL1, &blockInfo, - state, skipFeeCharge, skipValidate, errOnRevert, true, true, isEstimateFee, false) + var executionResults vm.ExecutionResults + if isEstimateFee { + executionResults, err = h.vm.EstimateFee(txns, classes, paidFeesOnL1, &blockInfo, + state, vm.EstimateFeeOptions{ + SkipValidate: skipValidate, + }) + } else { + executionResults, err = h.vm.Simulate(txns, classes, paidFeesOnL1, &blockInfo, + state, vm.SimulateOptions{ + SkipChargeFee: skipFeeCharge, + SkipValidate: skipValidate, + ErrOnRevert: errOnRevert, + }) + } if err != nil { return nil, httpHeader, handleExecutionError(err) } diff --git a/rpc/v8/simulation_test.go b/rpc/v8/simulation_test.go index c09183a9a1..2838f58aa6 100644 --- a/rpc/v8/simulation_test.go +++ b/rpc/v8/simulation_test.go @@ -66,9 +66,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, true, false, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipChargeFee: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -89,9 +89,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -111,9 +111,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, Cause: json.RawMessage("oops"), @@ -133,9 +133,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{&felt.Zero}, DataAvailability: []core.DataAvailability{{L1Gas: 0}, {L1Gas: 0}}, diff --git a/rpc/v8/trace.go b/rpc/v8/trace.go index 09db39a60e..759457d16f 100644 --- a/rpc/v8/trace.go +++ b/rpc/v8/trace.go @@ -233,8 +233,8 @@ func (h *Handler) traceBlockTransactionWithVM(block *core.Block) ( BlockHashToBeRevealed: blockHashToBeRevealed, } - executionResult, err := h.vm.Execute(block.Transactions, classes, paidFeesOnL1, - &blockInfo, state, false, false, false, true, false, false, false) + executionResult, err := h.vm.Trace(block.Transactions, classes, paidFeesOnL1, + &blockInfo, state, vm.TraceOptions{}) httpHeader.Set(ExecutionStepsHeader, strconv.FormatUint(executionResult.NumSteps, 10)) diff --git a/rpc/v8/trace_test.go b/rpc/v8/trace_test.go index 3989e94545..650ec2f040 100644 --- a/rpc/v8/trace_test.go +++ b/rpc/v8/trace_test.go @@ -370,19 +370,13 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false).Return(vm.ExecutionResults{ + vm.TraceOptions{}).Return(vm.ExecutionResults{ OverallFees: overallFee, GasConsumed: gc, Traces: []vm.TransactionTrace{*vmTrace}, @@ -602,11 +596,11 @@ func TestTraceBlockTransactions(t *testing.T) { // PendingState() in v8 always returns HeadState mockReader.EXPECT().HeadState().Return(headState, nopCloser, nil) - // vm.Execute is called with empty txns; use Any() for fields with dynamic content - mockVM.EXPECT().Execute( + // vm.Trace is called with empty txns; use Any() for fields with dynamic content + mockVM.EXPECT().Trace( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - false, false, false, true, false, false, false, + vm.TraceOptions{}, ).Return(vm.ExecutionResults{}, nil) blockID := blockIDPending(t) @@ -668,19 +662,13 @@ func TestTraceBlockTransactions(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false).Return(vm.ExecutionResults{ + vm.TraceOptions{}).Return(vm.ExecutionResults{ OverallFees: nil, DataAvailability: []core.DataAvailability{{}, {}}, GasConsumed: []core.GasConsumed{{}, {}}, diff --git a/rpc/v9/estimate_fee.go b/rpc/v9/estimate_fee.go index 4c72587e97..b62a559360 100644 --- a/rpc/v9/estimate_fee.go +++ b/rpc/v9/estimate_fee.go @@ -281,7 +281,7 @@ func (h *Handler) EstimateFee( ctx, id, broadcastedTxns.Data, - append(simulationFlags, SkipFeeChargeFlag), + simulationFlags, true, true, ) diff --git a/rpc/v9/estimate_fee_test.go b/rpc/v9/estimate_fee_test.go index 2d6f1ee774..158b2b321b 100644 --- a/rpc/v9/estimate_fee_test.go +++ b/rpc/v9/estimate_fee_test.go @@ -38,15 +38,13 @@ func TestEstimateFee(t *testing.T) { blockInfo := vm.BlockInfo{Header: &core.Header{}} t.Run("ok with zero values", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - false, - true, true, true, true, false). + vm.EstimateFeeOptions{}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -66,15 +64,15 @@ func TestEstimateFee(t *testing.T) { }) t.Run("ok with zero values, skip validate", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - true, - true, true, true, true, false). + vm.EstimateFeeOptions{ + SkipValidate: true, + }). Return( vm.ExecutionResults{ OverallFees: []*felt.Felt{}, @@ -97,15 +95,15 @@ func TestEstimateFee(t *testing.T) { }) t.Run("transaction execution error", func(t *testing.T) { - mockVM.EXPECT().Execute( + mockVM.EXPECT().EstimateFee( []core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, - true, - true, - true, true, true, true, false). + vm.EstimateFeeOptions{ + SkipValidate: true, + }). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, Cause: json.RawMessage("oops"), diff --git a/rpc/v9/simulation.go b/rpc/v9/simulation.go index 2833fc0c00..d4849b30e0 100644 --- a/rpc/v9/simulation.go +++ b/rpc/v9/simulation.go @@ -120,20 +120,32 @@ func (h *Handler) simulateTransactions( BlockHashToBeRevealed: blockHashToBeRevealed, } - executionResults, err := h.vm.Execute( - txns, - classes, - paidFeesOnL1, - &blockInfo, - state, - skipFeeCharge, - skipValidate, - errOnRevert, - true, - true, - isEstimateFee, - false, - ) + var executionResults vm.ExecutionResults + if isEstimateFee { + executionResults, err = h.vm.EstimateFee( + txns, + classes, + paidFeesOnL1, + &blockInfo, + state, + vm.EstimateFeeOptions{ + SkipValidate: skipValidate, + }, + ) + } else { + executionResults, err = h.vm.Simulate( + txns, + classes, + paidFeesOnL1, + &blockInfo, + state, + vm.SimulateOptions{ + SkipChargeFee: skipFeeCharge, + SkipValidate: skipValidate, + ErrOnRevert: errOnRevert, + }, + ) + } if err != nil { return nil, httpHeader, handleExecutionError(err) } diff --git a/rpc/v9/simulation_test.go b/rpc/v9/simulation_test.go index 62326dfb12..b819349f09 100644 --- a/rpc/v9/simulation_test.go +++ b/rpc/v9/simulation_test.go @@ -62,9 +62,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, true, false, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipChargeFee: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -85,9 +85,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -107,9 +107,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, Cause: json.RawMessage("oops"), @@ -129,9 +129,9 @@ func TestSimulateTransactions(t *testing.T) { mockState *mocks.MockStateReader, ) { defaultMockBehavior(mockReader, mockVM, mockState) - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ + mockVM.EXPECT().Simulate([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, false, true, false, true, true, false, false). + }, mockState, vm.SimulateOptions{SkipValidate: true}). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{&felt.Zero}, DataAvailability: []core.DataAvailability{{L1Gas: 0}, {L1Gas: 0}}, diff --git a/rpc/v9/trace.go b/rpc/v9/trace.go index e5554dd4ac..9d42709bd6 100644 --- a/rpc/v9/trace.go +++ b/rpc/v9/trace.go @@ -188,7 +188,7 @@ func (h *Handler) Call(funcCall *FunctionCall, id *BlockID) ([]*felt.Felt, *json // // Parameters: // -// - vm: The virtual machine used for execution +// - runner: The virtual machine used for execution // // - transactions: The transactions to trace // @@ -199,7 +199,7 @@ func (h *Handler) Call(funcCall *FunctionCall, id *BlockID) ([]*felt.Felt, *json // // - blockInfo: Block context for execution func traceTransactionsWithState( - vm vm.VM, + runner vm.VM, transactions []core.Transaction, executionState core.StateReader, classLookupState core.StateReader, @@ -216,19 +216,13 @@ func traceTransactionsWithState( return nil, httpHeader, err } - executionResult, vmErr := vm.Execute( + executionResult, vmErr := runner.Trace( transactions, declaredClasses, paidFeesOnL1, blockInfo, executionState, - false, // skipValidate - false, // skipFeeCharge - false, // skipNonceCharge - true, // allowZeroMaxFee - false, // allowNoSignature - false, // isEstimateFee - false, // returnInitialReads + vm.TraceOptions{}, ) httpHeader.Set(ExecutionStepsHeader, strconv.FormatUint(executionResult.NumSteps, 10)) diff --git a/rpc/v9/trace_test.go b/rpc/v9/trace_test.go index f0bcff05d9..1bdd5d7c0f 100644 --- a/rpc/v9/trace_test.go +++ b/rpc/v9/trace_test.go @@ -403,19 +403,13 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false).Return(vm.ExecutionResults{ + vm.TraceOptions{}).Return(vm.ExecutionResults{ OverallFees: overallFee, GasConsumed: gc, Traces: []vm.TransactionTrace{vmTrace}, @@ -481,19 +475,13 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, nil, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false, + vm.TraceOptions{}, ). Return(vm.ExecutionResults{ OverallFees: overallFee, @@ -576,19 +564,13 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false, + vm.TraceOptions{}, ). Return(vm.ExecutionResults{ OverallFees: overallFee, @@ -848,19 +830,13 @@ func TestTraceBlockTransactions(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" - mockVM.EXPECT().Execute( + mockVM.EXPECT().Trace( []core.Transaction{tx}, []core.ClassDefinition{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), - false, - false, - false, - true, - false, - false, - false).Return(vm.ExecutionResults{ + vm.TraceOptions{}).Return(vm.ExecutionResults{ OverallFees: nil, DataAvailability: []core.DataAvailability{{}, {}}, GasConsumed: []core.GasConsumed{{}, {}}, diff --git a/vm/rust/src/entrypoint/execute/execute.rs b/vm/rust/src/entrypoint/execute/execute.rs index f9666c3e55..1f5755723a 100644 --- a/vm/rust/src/entrypoint/execute/execute.rs +++ b/vm/rust/src/entrypoint/execute/execute.rs @@ -64,7 +64,7 @@ pub fn cairo_vm_execute( VecDeque::new() }; - let mut paid_fees_on_l1: VecDeque> = parse_json(paid_fees_on_l1_json)?; + let mut paid_fees_on_l1: VecDeque = parse_json(paid_fees_on_l1_json)?; let mut state = CachedState::new(reader); let concurrency_mode = concurrency_mode == 1; @@ -82,7 +82,7 @@ pub fn cairo_vm_execute( let mut writer_buffer = Vec::with_capacity(10_000); for (txn_index, txn_and_query_bit) in txns_and_query_bits.iter().enumerate() { - let class_info = match txn_and_query_bit.txn.clone() { + let class_info = match &txn_and_query_bit.txn { StarknetApiTransaction::Declare(_) => { let class_json_str = classes.pop_front().ok_or_else(|| { JunoError::tx_non_execution_error("missing declared class", txn_index) @@ -96,12 +96,12 @@ pub fn cairo_vm_execute( _ => None, }; - let paid_fee_on_l1: Option = match txn_and_query_bit.txn.clone() { + let paid_fee_on_l1: Option = match &txn_and_query_bit.txn { StarknetApiTransaction::L1Handler(_) => { let paid_fee_on_l1 = paid_fees_on_l1.pop_front().ok_or_else(|| { JunoError::tx_non_execution_error("missing fee paid on l1", txn_index) })?; - Some(*paid_fee_on_l1) + Some(paid_fee_on_l1) } _ => None, }; diff --git a/vm/vm.go b/vm/vm.go index 88c5307628..ba86c90c65 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -45,6 +45,45 @@ type CallResult struct { ExecutionFailed bool } +// executeOptions carries every flag accepted by the underlying VM. +// Used internally by the execute method +type executeOptions struct { + SkipChargeFee bool + SkipValidate bool + ErrOnRevert bool + ErrStack bool + AllowBinarySearch bool + IsEstimateFee bool + ReturnInitialReads bool +} + +// SimulateOptions carries the flags relevant to RPC simulate. +type SimulateOptions struct { + SkipChargeFee bool + SkipValidate bool + ErrOnRevert bool + ReturnInitialReads bool +} + +// EstimateFeeOptions carries the flags relevant to RPC estimateFee. +type EstimateFeeOptions struct { + SkipValidate bool + ReturnInitialReads bool +} + +// TraceOptions carries the flags relevant to replaying an existing block. +type TraceOptions struct { + ReturnInitialReads bool +} + +// BuildBlockOptions carries flags used when producing a block (builder or +// genesis bootstrap). +type BuildBlockOptions struct { + SkipChargeFee bool + SkipValidate bool + ErrOnRevert bool +} + //go:generate mockgen -destination=../mocks/mock_vm.go -package=mocks github.com/NethermindEth/juno/vm VM type VM interface { Call( @@ -56,19 +95,37 @@ type VM interface { structuredErrStack, returnStateDiff bool, ) (CallResult, error) - Execute( + Simulate( txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *BlockInfo, state core.StateReader, - skipChargeFee, - skipValidate, - errOnRevert, - errStack, - allowBinarySearch bool, - isEstimateFee bool, - returnInitialReads bool, + opts SimulateOptions, + ) (ExecutionResults, error) + EstimateFee( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts EstimateFeeOptions, + ) (ExecutionResults, error) + Trace( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts TraceOptions, + ) (ExecutionResults, error) + BuildBlock( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts BuildBlockOptions, ) (ExecutionResults, error) } @@ -338,69 +395,71 @@ func (v *vm) Call( } } } - return CallResult{Result: context.response, StateDiff: stateDiff, ExecutionFailed: context.executionFailed}, nil + return CallResult{ + Result: context.response, + StateDiff: stateDiff, + ExecutionFailed: context.executionFailed, + }, nil +} + +// cgoExecutionInputs holds the C-side resources for one execution call. +// Always pair with `defer inputs.free()`. +type cgoExecutionInputs struct { + handle cgo.Handle + context *callContext + txnsCStr *C.char + classesCStr *C.char + feesCStr *C.char + blockInfo C.BlockInfo + chainInfo C.ChainInfo } -// Execute executes a given transaction set and returns the gas spent per transaction -func (v *vm) Execute( +func (i *cgoExecutionInputs) free() { + C.free(unsafe.Pointer(i.classesCStr)) + C.free(unsafe.Pointer(i.feesCStr)) + C.free(unsafe.Pointer(i.txnsCStr)) + C.free(unsafe.Pointer(i.chainInfo.chain_id)) + C.free(unsafe.Pointer(i.blockInfo.version)) + i.handle.Delete() +} + +// prepareExecutionInputs builds every C-side resource needed to invoke any +// of the cairoVM* execution entry points. +func (v *vm) prepareExecutionInputs( txns []core.Transaction, declaredClasses []core.ClassDefinition, paidFeesOnL1 []*felt.Felt, blockInfo *BlockInfo, state core.StateReader, - skipChargeFee, - skipValidate, - errOnRevert, - errorStack, - allowBinarySearch bool, - isEstimateFee bool, - returnInitialReads bool, -) (ExecutionResults, error) { - context := &callContext{ - state: state, - logger: v.logger, - } +) (*cgoExecutionInputs, error) { + context := &callContext{state: state, logger: v.logger} handle := cgo.NewHandle(context) - defer handle.Delete() txnsJSON, classesJSON, err := marshalTxnsAndDeclaredClasses(txns, declaredClasses) if err != nil { - return ExecutionResults{}, err + handle.Delete() + return nil, err } paidFeesOnL1Bytes, err := json.Marshal(paidFeesOnL1) if err != nil { - return ExecutionResults{}, err + handle.Delete() + return nil, err } - paidFeesOnL1CStr := cstring(paidFeesOnL1Bytes) - txnsJSONCstr := cstring(txnsJSON) - classesJSONCStr := cstring(classesJSON) - - cBlockInfo := makeCBlockInfo(blockInfo) - cChainInfo := makeCChainInfo(v.chainInfo) - C.cairoVMExecute(txnsJSONCstr, - classesJSONCStr, - paidFeesOnL1CStr, - &cBlockInfo, - &cChainInfo, - C.uintptr_t(handle), - toUchar(skipChargeFee), - toUchar(skipValidate), - toUchar(errOnRevert), - toUchar(v.concurrencyMode), - toUchar(errorStack), - toUchar(allowBinarySearch), - toUchar(isEstimateFee), //nolint:gocritic // See https://github.com/go-critic/go-critic/issues/897 - toUchar(returnInitialReads), //nolint:gocritic // false positive - ) - - C.free(unsafe.Pointer(classesJSONCStr)) - C.free(unsafe.Pointer(paidFeesOnL1CStr)) - C.free(unsafe.Pointer(txnsJSONCstr)) - C.free(unsafe.Pointer(cChainInfo.chain_id)) - C.free(unsafe.Pointer(cBlockInfo.version)) + return &cgoExecutionInputs{ + handle: handle, + context: context, + txnsCStr: cstring(txnsJSON), + classesCStr: cstring(classesJSON), + feesCStr: cstring(paidFeesOnL1Bytes), + blockInfo: makeCBlockInfo(blockInfo), + chainInfo: makeCChainInfo(v.chainInfo), + }, nil +} +// parseExecutionResults converts callContext into ExecutionResults. +func parseExecutionResults(context *callContext) (ExecutionResults, error) { if context.err != "" { if context.errTxnIndex >= 0 { return ExecutionResults{}, TransactionExecutionError{ @@ -413,22 +472,25 @@ func (v *vm) Execute( traces := make([]TransactionTrace, len(context.traces)) for index, traceJSON := range context.traces { - if err := json.Unmarshal(traceJSON, &traces[index]); err != nil { - return ExecutionResults{}, fmt.Errorf("unmarshal trace: %v", err) + err := json.Unmarshal(traceJSON, &traces[index]) + if err != nil { + return ExecutionResults{}, fmt.Errorf("unmarshal trace: %w", err) } } receipts := make([]TransactionReceipt, len(context.receipts)) - for index, traceJSON := range context.receipts { - if err := json.Unmarshal(traceJSON, &receipts[index]); err != nil { - return ExecutionResults{}, fmt.Errorf("unmarshal receipt: %v", err) + for index, receiptJSON := range context.receipts { + err := json.Unmarshal(receiptJSON, &receipts[index]) + if err != nil { + return ExecutionResults{}, fmt.Errorf("unmarshal receipt: %w", err) } } var initialReads *InitialReads if len(context.initialReads) > 0 { var reads InitialReads - if err := json.Unmarshal(context.initialReads, &reads); err != nil { - return ExecutionResults{}, fmt.Errorf("unmarshal initial reads: %v", err) + err := json.Unmarshal(context.initialReads, &reads) + if err != nil { + return ExecutionResults{}, fmt.Errorf("unmarshal initial reads: %w", err) } initialReads = &reads } @@ -444,6 +506,123 @@ func (v *vm) Execute( }, nil } +// execute is the generic entry point exposing full RPC control. +// Kept unexported so external callers must use Simulate / Trace / BuildBlock. +func (v *vm) execute( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts executeOptions, +) (ExecutionResults, error) { + inputs, err := v.prepareExecutionInputs(txns, declaredClasses, paidFeesOnL1, blockInfo, state) + if err != nil { + return ExecutionResults{}, err + } + defer inputs.free() + + C.cairoVMExecute( + inputs.txnsCStr, inputs.classesCStr, inputs.feesCStr, + &inputs.blockInfo, &inputs.chainInfo, C.uintptr_t(inputs.handle), + toUchar(opts.SkipChargeFee), + toUchar(opts.SkipValidate), + toUchar(opts.ErrOnRevert), + toUchar(v.concurrencyMode), + toUchar(opts.ErrStack), + toUchar(opts.AllowBinarySearch), + toUchar(opts.IsEstimateFee), + //nolint:gocritic // false positive: dupSubExpr with cgo toUchar(opts.ReturnInitialReads), + toUchar(opts.ReturnInitialReads), + ) + + return parseExecutionResults(inputs.context) +} + +// Simulate runs the txn set under RPC simulate semantics. +func (v *vm) Simulate( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts SimulateOptions, +) (ExecutionResults, error) { + return v.execute( + txns, declaredClasses, paidFeesOnL1, blockInfo, state, + executeOptions{ + SkipChargeFee: opts.SkipChargeFee, + SkipValidate: opts.SkipValidate, + ErrOnRevert: opts.ErrOnRevert, + ErrStack: true, + AllowBinarySearch: true, + ReturnInitialReads: opts.ReturnInitialReads, + }, + ) +} + +// EstimateFee runs the txn set under RPC estimateFee semantics. +// Fee charging is always skipped. +func (v *vm) EstimateFee( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts EstimateFeeOptions, +) (ExecutionResults, error) { + return v.execute( + txns, declaredClasses, paidFeesOnL1, blockInfo, state, + executeOptions{ + SkipChargeFee: true, + SkipValidate: opts.SkipValidate, + ErrOnRevert: true, + ErrStack: true, + AllowBinarySearch: true, + IsEstimateFee: true, + ReturnInitialReads: opts.ReturnInitialReads, + }, + ) +} + +// Trace replays an existing block. +func (v *vm) Trace( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts TraceOptions, +) (ExecutionResults, error) { + return v.execute( + txns, declaredClasses, paidFeesOnL1, blockInfo, state, + executeOptions{ + ErrStack: true, + ReturnInitialReads: opts.ReturnInitialReads, + }, + ) +} + +// BuildBlock executes txns in block-production mode (builder or genesis). +func (v *vm) BuildBlock( + txns []core.Transaction, + declaredClasses []core.ClassDefinition, + paidFeesOnL1 []*felt.Felt, + blockInfo *BlockInfo, + state core.StateReader, + opts BuildBlockOptions, +) (ExecutionResults, error) { + return v.execute( + txns, declaredClasses, paidFeesOnL1, blockInfo, state, + executeOptions{ + SkipChargeFee: opts.SkipChargeFee, + SkipValidate: opts.SkipValidate, + ErrOnRevert: opts.ErrOnRevert, + ErrStack: true, + }, + ) +} + func marshalTxnsAndDeclaredClasses( txns []core.Transaction, declaredClasses []core.ClassDefinition, diff --git a/vm/vm_test.go b/vm/vm_test.go index 6f0b5e20c2..cb152c635e 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -371,8 +371,8 @@ func TestExecute(t *testing.T) { ChainID: networks.Mainnet.L2ChainID, FeeTokenAddresses: feeTokens, } - _, err := New(&chainInfo, false, nil). - Execute([]core.Transaction{}, []core.ClassDefinition{}, []*felt.Felt{}, &BlockInfo{ + _, err := New(&chainInfo, false, nil).(*vm). + execute([]core.Transaction{}, []core.ClassDefinition{}, []*felt.Felt{}, &BlockInfo{ Header: &core.Header{ Timestamp: 1666877926, SequencerAddress: felt.NewUnsafeFromString[felt.Felt]( @@ -381,8 +381,7 @@ func TestExecute(t *testing.T) { L1GasPriceETH: &felt.Zero, L1GasPriceSTRK: &felt.Zero, }, - }, state, - false, false, false, false, false, false, false) + }, state, executeOptions{}) require.NoError(t, err) }) t.Run("zero data", func(t *testing.T) { @@ -391,13 +390,13 @@ func TestExecute(t *testing.T) { ChainID: networks.Mainnet.L2ChainID, FeeTokenAddresses: feeTokens, } - _, err := New(&chainInfo, false, nil).Execute(nil, nil, []*felt.Felt{}, &BlockInfo{ + _, err := New(&chainInfo, false, nil).(*vm).execute(nil, nil, []*felt.Felt{}, &BlockInfo{ Header: &core.Header{ SequencerAddress: &felt.Zero, L1GasPriceETH: &felt.Zero, L1GasPriceSTRK: &felt.Zero, }, - }, state, false, false, false, false, false, false, false) + }, state, executeOptions{}) require.NoError(t, err) }) }